updated actionbarsherlock to 3.5.0

pull/14/head
Arne Jans 13 years ago
parent cae873ae69
commit fc99549dec

@ -1,5 +1,6 @@
#Android generated
bin
library/bin
gen
#Eclipse

@ -1,7 +1,35 @@
Change Log
===============================================================================
Version 3.4.2 *(2001-11-10)*
Version 3.5.0 *(2011-12-18)*
----------------------------
* Library now uses the `r6` version of the compatibility library for its base.
Ice Cream Sandwich-specific implementations are currently disabled, however,
but will be added in a future version of the library.
`MenuCompat`, `MenuItemCompat`, and `ActivityCompat` have be added back in
to ease transition to this library but all their methods and the classes
themselves have been deprecated.
* Rewritten menu and action item support from Ice Cream Sandwich.
* Removed the need for the custom `Window.FEATURE_ACTION_ITEM_TEXT` flag.
You should now use the `showAsAction` attribute and/or the
`setShowAsAction(int)` method on each `MenuItem` to control whether or
not text is shown
* Action item dividers are now added automatically only when necessary
to distinguish possible confusion between action items.
* Fix: Action views now properly size themselves within the bounded space
of the menu.
* Fix: List navigation no longer becomes unusable on certain device
configurations.
* Fix: `SubMenu`'s `findItem(int)` method now properly returns the support
version of `MenuItem`.
* Fix: Invisible sub-menu items are no longer shown on the pre-3.0 popup list.
Version 3.4.2 *(2011-11-09)*
----------------------------
* Fix: Stacked action bar now properly sets the tab bar background based on

@ -3,8 +3,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.actionbarsherlock"
android:versionCode="40"
android:versionName="3.4.2">
android:versionCode="50"
android:versionName="3.5.0">
<uses-sdk
android:minSdkVersion="4"

@ -5,7 +5,7 @@
<module name="Checker">
<!--module name="NewlineAtEndOfFile"/-->
<module name="FileLength"/>
<!--module name="FileLength"/-->
<module name="FileTabCharacter"/>
<!-- Trailing spaces -->

@ -11,7 +11,7 @@
<parent>
<groupId>com.actionbarsherlock</groupId>
<artifactId>parent</artifactId>
<version>3.4.2</version>
<version>3.5.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -16,9 +16,33 @@
-->
<!--
This is an optimized layout for a screen with the Action Bar enabled.
This is an optimized layout for a screen with the Action Bar (Tab's inlined) enabled.
-->
<merge>
<include layout="@layout/abs__screen_action_bar_inline" />
</merge>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
>
<com.actionbarsherlock.internal.widget.ActionBarContainer
android:id="@+id/abs__action_bar_container"
android:layout_width="fill_parent"
android:layout_height="?attr/abHeight"
android:background="?attr/abBackground"
>
<com.actionbarsherlock.internal.widget.ActionBarView
android:id="@+id/abs__action_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</com.actionbarsherlock.internal.widget.ActionBarContainer>
<FrameLayout
android:id="@+id/abs__content"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay"
/>
</LinearLayout>

@ -17,9 +17,33 @@
<!--
This is an optimized layout for a screen with
the Action Bar enabled overlaying application content.
the Action Bar (Tab's inlined) enabled overlaying application content.
-->
<merge>
<include layout="@layout/abs__screen_action_bar_inline_overlay" />
</merge>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/abs__content"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<com.actionbarsherlock.internal.widget.ActionBarContainer
android:id="@+id/abs__action_bar_container"
android:layout_width="fill_parent"
android:layout_height="?attr/abHeight"
android:background="?attr/abBackground"
android:gravity="top">
<com.actionbarsherlock.internal.widget.ActionBarView
android:id="@+id/abs__action_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</com.actionbarsherlock.internal.widget.ActionBarContainer>
<ImageView
android:src="?android:attr/windowContentOverlay"
android:scaleType="fitXY"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/abs__action_bar_container" />
</RelativeLayout>

@ -16,9 +16,33 @@
-->
<!--
This is an optimized layout for a screen with the Action Bar enabled.
This is an optimized layout for a screen with the Action Bar (Tab's inlined) enabled.
-->
<merge>
<include layout="@layout/abs__screen_action_bar_inline" />
</merge>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
>
<com.actionbarsherlock.internal.widget.ActionBarContainer
android:id="@+id/abs__action_bar_container"
android:layout_width="fill_parent"
android:layout_height="?attr/abHeight"
android:background="?attr/abBackground"
>
<com.actionbarsherlock.internal.widget.ActionBarView
android:id="@+id/abs__action_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</com.actionbarsherlock.internal.widget.ActionBarContainer>
<FrameLayout
android:id="@+id/abs__content"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay"
/>
</LinearLayout>

@ -17,9 +17,33 @@
<!--
This is an optimized layout for a screen with
the Action Bar enabled overlaying application content.
the Action Bar (Tab's inlined) enabled overlaying application content.
-->
<merge>
<include layout="@layout/abs__screen_action_bar_inline_overlay" />
</merge>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/abs__content"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<com.actionbarsherlock.internal.widget.ActionBarContainer
android:id="@+id/abs__action_bar_container"
android:layout_width="fill_parent"
android:layout_height="?attr/abHeight"
android:background="?attr/abBackground"
android:gravity="top">
<com.actionbarsherlock.internal.widget.ActionBarView
android:id="@+id/abs__action_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</com.actionbarsherlock.internal.widget.ActionBarContainer>
<ImageView
android:src="?android:attr/windowContentOverlay"
android:scaleType="fitXY"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/abs__action_bar_container" />
</RelativeLayout>

@ -24,7 +24,7 @@
android:layout_width="wrap_content"
android:layout_height="fill_parent"/>
<LinearLayout
<FrameLayout
android:id="@+id/abs__actions"
android:layout_width="wrap_content"
android:layout_height="fill_parent"

@ -47,7 +47,7 @@
android:scaleType="center" />
</LinearLayout>
<LinearLayout
<FrameLayout
android:id="@+id/abs__actions"
android:layout_width="wrap_content"
android:layout_height="fill_parent"

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 Johan Nilsson <http://markupartist.com>
Copyright (C) 2011 Jake Wharton <jakewharton@gmail.com>
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.
-->
<com.actionbarsherlock.internal.view.menu.ActionMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:addStatesFromChildren="true"
android:background="?selectableItemBackground"
android:gravity="center"
android:minWidth="45dip"
android:focusable="true"
android:clickable="true"
>
<ImageView
android:id="@+id/abs__item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:gravity="center_vertical"
/>
<TextView
android:id="@+id/abs__item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/abs__item_icon"
android:gravity="center_vertical"
android:visibility="gone"
android:textColor="?attr/actionMenuTextColor"
style="?attr/actionMenuTextAppearance"
/>
<FrameLayout
android:id="@+id/abs__item_custom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:visibility="gone"
/>
</com.actionbarsherlock.internal.view.menu.ActionMenuItemView>

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<com.actionbarsherlock.internal.view.menu.ActionMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:addStatesFromChildren="true"
android:gravity="center"
android:focusable="true"
android:paddingLeft="4dip"
android:paddingRight="4dip"
style="?attr/actionButtonStyle">
<ImageButton android:id="@+id/abs__imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:layout_marginTop="4dip"
android:layout_marginBottom="4dip"
android:layout_marginLeft="4dip"
android:layout_marginRight="4dip"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:background="@null"
android:focusable="false" />
<Button android:id="@+id/abs__textButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:textAppearance="?attr/actionMenuTextAppearance"
android:textColor="?attr/actionMenuTextColor"
android:singleLine="true"
android:ellipsize="none"
android:background="@null"
android:paddingTop="4dip"
android:paddingBottom="4dip"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:focusable="false" />
</com.actionbarsherlock.internal.view.menu.ActionMenuItemView>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<com.actionbarsherlock.internal.view.menu.ActionMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:divider="?attr/abDivider"
android:dividerPadding="12dip"
android:gravity="center_vertical" />

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Copyright (C) 2011 Jake Wharton
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.
-->
<!--
This is an optimized layout for a screen with the Action Bar (Tab's inlined) enabled.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
>
<com.actionbarsherlock.internal.widget.ActionBarContainer
android:id="@+id/abs__action_bar_container"
android:layout_width="fill_parent"
android:layout_height="?attr/abHeight"
android:background="?attr/abBackground"
>
<com.actionbarsherlock.internal.widget.ActionBarView
android:id="@+id/abs__action_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</com.actionbarsherlock.internal.widget.ActionBarContainer>
<FrameLayout
android:id="@+id/abs__content"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay"
/>
</LinearLayout>

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Copyright (C) 2011 Jake Wharton
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.
-->
<!--
This is an optimized layout for a screen with
the Action Bar (Tab's inlined) enabled overlaying application content.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/abs__content"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<com.actionbarsherlock.internal.widget.ActionBarContainer
android:id="@+id/abs__action_bar_container"
android:layout_width="fill_parent"
android:layout_height="?attr/abHeight"
android:background="?attr/abBackground"
android:gravity="top">
<com.actionbarsherlock.internal.widget.ActionBarView
android:id="@+id/abs__action_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</com.actionbarsherlock.internal.widget.ActionBarContainer>
<ImageView
android:src="?android:attr/windowContentOverlay"
android:scaleType="fitXY"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/abs__action_bar_container" />
</RelativeLayout>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/dimens.xml
**
** Copyright 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.
*/
-->
<resources>
<!-- Default height of an action bar. -->
<dimen name="abs__action_bar_default_height">40dip</dimen>
</resources>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/dimens.xml
**
** Copyright 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.
*/
-->
<resources>
<!-- Default height of an action bar. -->
<dimen name="abs__action_bar_default_height">48dip</dimen>
</resources>

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2009, 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.
*/
-->
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Whether action menu items should obey the "withText" showAsAction
flag. This may be set to false for situations where space is
extremely limited. -->
<bool name="abs__config_allowActionMenuItemTextWithIcon">true</bool>
</resources>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/dimens.xml
**
** Copyright 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.
*/
-->
<resources>
<!-- Default height of an action bar. -->
<dimen name="abs__action_bar_default_height">56dip</dimen>
</resources>

@ -2,7 +2,7 @@
<resources>
<style name="Theme.Sherlock" parent="android:Theme.Holo">
<item name="actionBarSize">56dip</item>
<item name="actionBarSize">@dimen/abs__action_bar_default_height</item>
<item name="actionBarTabStyle">@style/Widget.Sherlock.ActionBar.TabView</item>
<item name="actionBarTabBarStyle">@style/Widget.Sherlock.ActionBar.TabBar</item>
<item name="actionBarTabTextStyle">@style/Widget.Sherlock.ActionBar.TabText</item>
@ -20,7 +20,9 @@
<item name="popupMenuStyle">@style/Widget.Sherlock.PopupMenu</item>
<item name="selectableItemBackground">@drawable/abs__item_background_holo_dark</item>
<item name="windowActionBar">true</item>
<item name="windowActionBarOverlay">false</item>
<item name="windowActionModeOverlay">false</item>
<item name="windowNoTitle">false</item>
<!-- These should be in `Widget.Sherlock.ActionBar` but there is no way to then -->
<!-- reference them as sibling attributes to their native counterparts. See: -->
@ -61,7 +63,9 @@
<item name="android:popupMenuStyle">?popupMenuStyle</item>
<item name="android:selectableItemBackground">?selectableItemBackground</item>
<item name="android:windowActionBar">?windowActionBar</item>
<item name="android:windowActionBarOverlay">?windowActionBarOverlay</item>
<item name="android:windowActionModeOverlay">?windowActionModeOverlay</item>
<item name="android:windowNoTitle">?windowNoTitle</item>
</style>
<style name="Internal.Widget.Sherlock.ActionBar" parent="android:Widget.Holo.ActionBar">
@ -96,7 +100,7 @@
<style name="Theme.Sherlock.Light" parent="android:Theme.Holo.Light">
<item name="actionBarSize">56dip</item>
<item name="actionBarSize">@dimen/abs__action_bar_default_height</item>
<item name="actionBarTabStyle">@style/Widget.Sherlock.Light.ActionBar.TabView</item>
<item name="actionBarTabBarStyle">@style/Widget.Sherlock.Light.ActionBar.TabBar</item>
<item name="actionBarTabTextStyle">@style/Widget.Sherlock.Light.ActionBar.TabText</item>
@ -114,7 +118,9 @@
<item name="popupMenuStyle">@style/Widget.Sherlock.Light.PopupMenu</item>
<item name="selectableItemBackground">@drawable/abs__item_background_holo_light</item>
<item name="windowActionBar">true</item>
<item name="windowActionBarOverlay">false</item>
<item name="windowActionModeOverlay">false</item>
<item name="windowNoTitle">false</item>
<!-- These should be in `Widget.Sherlock.ActionBar` but there is no way to then -->
<!-- reference them as sibling attributes to their native counterparts. See: -->
@ -154,7 +160,9 @@
<item name="android:popupMenuStyle">?popupMenuStyle</item>
<item name="android:selectableItemBackground">?selectableItemBackground</item>
<item name="android:windowActionBar">?windowActionBar</item>
<item name="android:windowActionBarOverlay">?windowActionBarOverlay</item>
<item name="android:windowActionModeOverlay">?windowActionModeOverlay</item>
<item name="android:windowNoTitle">?windowNoTitle</item>
</style>
<style name="Internal.Widget.Sherlock.Light.ActionBar" parent="android:Widget.Holo.Light.ActionBar">

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/dimens.xml
**
** Copyright 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.
*/
-->
<resources>
<!-- Default height of an action bar. -->
<dimen name="abs__action_bar_default_height">56dip</dimen>
</resources>

@ -24,7 +24,10 @@
<attr name="popupMenuStyle" format="reference" />
<attr name="dropdownListPreferredItemHeight" format="dimension" />
<attr name="actionSpinnerItemStyle" format="reference" />
<attr name="windowNoTitle" format="boolean"/>
<attr name="windowActionBar" format="boolean"/>
<attr name="windowActionBarOverlay" format="boolean"/>
<attr name="windowActionModeOverlay" format="boolean"/>

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2009, 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.
*/
-->
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Whether action menu items should obey the "withText" showAsAction
flag. This may be set to false for situations where space is
extremely limited. -->
<bool name="abs__config_allowActionMenuItemTextWithIcon">false</bool>
<color name="abs__item_focused">#FF6899FF</color>
<integer name="abs__max_action_buttons">3</integer>
<string name="abs__tab_under_ab_tag">tabUnderAb</string>
</resources>

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="abs__item_focused">#FF6899FF</color>
<integer name="abs__max_action_buttons">3</integer>
<string name="abs__tab_under_ab_tag">tabUnderAb</string>
</resources>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/dimens.xml
**
** Copyright 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.
*/
-->
<resources>
<!-- Default height of an action bar. -->
<dimen name="abs__action_bar_default_height">48dip</dimen>
</resources>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2007, 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.
*/
-->
<resources>
<item type="id" name="abs__action_menu_presenter" />
</resources>

@ -2,7 +2,7 @@
<resources>
<style name="Theme.Sherlock" parent="android:Theme.NoTitleBar">
<item name="actionBarSize">45dip</item>
<item name="actionBarSize">@dimen/abs__action_bar_default_height</item>
<item name="actionBarTabStyle">@style/Widget.Sherlock.ActionBar.TabView</item>
<item name="actionBarTabBarStyle">@style/Widget.Sherlock.ActionBar.TabBar</item>
<item name="actionBarTabTextStyle">@style/Widget.Sherlock.ActionBar.TabText</item>
@ -10,7 +10,7 @@
<item name="actionDropDownStyle">@style/Widget.Sherlock.Spinner</item>
<item name="actionHomeButtonStyle">@style/Widget.Sherlock.ActionButton.Home</item>
<item name="actionMenuTextAppearance">?android:attr/textAppearanceMedium</item>
<item name="actionMenuTextColor">?android:attr/textColorPrimary</item>
<item name="actionMenuTextColor">?android:attr/textColorPrimaryNoDisable</item>
<item name="actionModeBackground">@drawable/abs__cab_background_holo_dark</item>
<item name="actionModeCloseDrawable">@drawable/abs__cab_ic_close_holo</item>
<item name="actionModeCloseButtonStyle">@style/Widget.Sherlock.ActionButton.CloseMode</item>
@ -22,7 +22,9 @@
<item name="popupMenuStyle">@style/Widget.Sherlock.PopupMenu</item>
<item name="selectableItemBackground">@color/abs__item_bg</item>
<item name="windowActionBar">true</item>
<item name="windowActionBarOverlay">false</item>
<item name="windowActionModeOverlay">false</item>
<item name="windowNoTitle">false</item>
<!-- These should be in `Widget.Sherlock.ActionBar` but there is no way to then -->
<!-- reference them as sibling attributes to their native counterparts. See: -->
@ -63,10 +65,12 @@
</style>
<style name="Widget.Sherlock.ActionButton" parent="android:Widget">
<item name="android:background">?selectableItemBackground</item>
<item name="android:background">?attr/selectableItemBackground</item>
<item name="android:minHeight">?attr/actionBarSize</item>
<item name="android:minWidth">48dip</item>
<item name="android:gravity">center</item>
<item name="android:paddingLeft">6dip</item>
<item name="android:paddingRight">6dip</item>
<item name="android:paddingLeft">12dip</item>
<item name="android:paddingRight">12dip</item>
<item name="android:scaleType">center</item>
</style>
@ -108,7 +112,7 @@
<style name="Theme.Sherlock.Light" parent="android:Theme.Light.NoTitleBar">
<item name="actionBarSize">45dip</item>
<item name="actionBarSize">@dimen/abs__action_bar_default_height</item>
<item name="actionBarTabStyle">@style/Widget.Sherlock.Light.ActionBar.TabView</item>
<item name="actionBarTabBarStyle">@style/Widget.Sherlock.Light.ActionBar.TabBar</item>
<item name="actionBarTabTextStyle">@style/Widget.Sherlock.Light.ActionBar.TabText</item>
@ -116,7 +120,7 @@
<item name="actionDropDownStyle">@style/Widget.Sherlock.Light.Spinner</item>
<item name="actionHomeButtonStyle">@style/Widget.Sherlock.Light.ActionButton.Home</item>
<item name="actionMenuTextAppearance">?android:attr/textAppearanceMedium</item>
<item name="actionMenuTextColor">?android:attr/textColorPrimary</item>
<item name="actionMenuTextColor">?android:attr/textColorPrimaryNoDisable</item>
<item name="actionModeBackground">@drawable/abs__cab_background_holo_light</item>
<item name="actionModeCloseDrawable">@drawable/abs__cab_ic_close_holo</item>
<item name="actionModeCloseButtonStyle">@style/Widget.Sherlock.Light.ActionButton.CloseMode</item>
@ -128,7 +132,9 @@
<item name="popupMenuStyle">@style/Widget.Sherlock.Light.PopupMenu</item>
<item name="selectableItemBackground">@color/abs__item_bg</item>
<item name="windowActionBar">true</item>
<item name="windowActionBarOverlay">false</item>
<item name="windowActionModeOverlay">false</item>
<item name="windowNoTitle">false</item>
<!-- These should be in `Widget.Sherlock.ActionBar` but there is no way to then -->
<!-- reference them as sibling attributes to their native counterparts. See: -->
@ -169,10 +175,12 @@
</style>
<style name="Widget.Sherlock.Light.ActionButton" parent="android:Widget">
<item name="android:background">?selectableItemBackground</item>
<item name="android:background">?attr/selectableItemBackground</item>
<item name="android:minHeight">?attr/actionBarSize</item>
<item name="android:minWidth">48dip</item>
<item name="android:gravity">center</item>
<item name="android:paddingLeft">6dip</item>
<item name="android:paddingRight">6dip</item>
<item name="android:paddingLeft">12dip</item>
<item name="android:paddingRight">12dip</item>
<item name="android:scaleType">center</item>
</style>

@ -184,22 +184,34 @@ public class AccessibilityServiceInfoCompat {
* @return The string representation.
*/
public static String feedbackTypeToString(int feedbackType) {
switch (feedbackType) {
case AccessibilityServiceInfo.FEEDBACK_AUDIBLE:
return "FEEDBACK_AUDIBLE";
case AccessibilityServiceInfo.FEEDBACK_HAPTIC:
return "FEEDBACK_HAPTIC";
case AccessibilityServiceInfo.FEEDBACK_GENERIC:
return "FEEDBACK_GENERIC";
case AccessibilityServiceInfo.FEEDBACK_SPOKEN:
return "FEEDBACK_SPOKEN";
case AccessibilityServiceInfo.FEEDBACK_VISUAL:
return "FEEDBACK_VISUAL";
case AccessibilityServiceInfoCompat.FEEDBACK_ALL_MASK:
return "FEEDBACK_ALL";
default:
return null;
StringBuilder builder = new StringBuilder();
builder.append("[");
while (feedbackType > 0) {
final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
feedbackType &= ~feedbackTypeFlag;
if (builder.length() > 1) {
builder.append(", ");
}
switch (feedbackTypeFlag) {
case AccessibilityServiceInfo.FEEDBACK_AUDIBLE:
builder.append("FEEDBACK_AUDIBLE");
break;
case AccessibilityServiceInfo.FEEDBACK_HAPTIC:
builder.append("FEEDBACK_HAPTIC");
break;
case AccessibilityServiceInfo.FEEDBACK_GENERIC:
builder.append("FEEDBACK_GENERIC");
break;
case AccessibilityServiceInfo.FEEDBACK_SPOKEN:
builder.append("FEEDBACK_SPOKEN");
break;
case AccessibilityServiceInfo.FEEDBACK_VISUAL:
builder.append("FEEDBACK_VISUAL");
break;
}
}
builder.append("]");
return builder.toString();
}
/**

@ -32,14 +32,6 @@ import android.widget.SpinnerAdapter;
* application.
*/
public abstract class ActionBar {
/**
* Return the actual public action bar instance. This will either return
* itself or null depending on the state of the underlying action bar.
*
* @return Action bar instance.
*/
protected abstract ActionBar getPublicInstance();
// ------------------------------------------------------------------------
// ACTION MODE SUPPORT
// ------------------------------------------------------------------------

@ -0,0 +1,41 @@
/*
* 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 android.support.v4.app;
import android.app.Activity;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Use {@link FragmentActivity}.
*/
@Deprecated
class ActivityCompatHoneycomb {
/** @Deprecated Use {@link FragmentActivity#invalidateOptionsMenu()}. */
@Deprecated
static void invalidateOptionsMenu(Activity activity) {
activity.invalidateOptionsMenu();
}
/** @Deprecated Use {@link FragmentActivity#dump(String, FileDescriptor, PrintWriter, String[])}. */
@Deprecated
static void dump(Activity activity, String prefix, FileDescriptor fd,
PrintWriter writer, String[] args) {
activity.dump(prefix, fd, writer, args);
}
}

@ -16,7 +16,6 @@
package android.support.v4.app;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@ -24,7 +23,6 @@ import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import com.actionbarsherlock.R;
final class BackStackState implements Parcelable {
final int[] mOps;
@ -166,8 +164,6 @@ final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
static final String TAG = "BackStackEntry";
private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
final FragmentManagerImpl mManager;
static final int OP_NULL = 0;
@ -359,12 +355,6 @@ final class BackStackRecord extends FragmentTransaction implements
}
if (containerViewId != 0) {
//This will change the target container ID to be the content view
//of our custom action bar implementation when the entire activity
//view is selected as the target and we are pre-honeycomb
if (!IS_HONEYCOMB && (containerViewId == android.R.id.content)) {
containerViewId = R.id.abs__content;
}
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId

@ -232,9 +232,6 @@ public class DialogFragment extends Fragment
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//SEE: http://stackoverflow.com/questions/5637894/dialogfragments-with-devices-api-level-11/7560686#7560686
//mShowsDialog = mContainerId == 0;
if (savedInstanceState != null) {
mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
mTheme = savedInstanceState.getInt(SAVED_THEME, 0);

@ -214,7 +214,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
// The fragment manager we are associated with. Set as soon as the
// fragment is used in a transaction; cleared after it has been removed
// from all transactions.
FragmentManager mFragmentManager;
FragmentManagerImpl mFragmentManager;
// Activity this fragment is attached to.
SupportActivity mActivity;
@ -267,6 +267,13 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
// The real inner view that will save/restore state.
View mInnerView;
// Whether this fragment should defer starting until after other fragments
// have been started and their loaders are finished.
boolean mDeferStart;
// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;
LoaderManagerImpl mLoaderManager;
boolean mLoadersStarted;
boolean mCheckedForLoaderManager;
@ -741,6 +748,35 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
}
}
/**
* Set a hint to the system about whether this fragment's UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
*
* <p>An app may set this to false to indicate that the fragment's UI is
* scrolled out of visibility or is otherwise not directly visible to the user.
* This may be used by the system to prioritize operations such as fragment lifecycle updates
* or loader ordering behavior.</p>
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = !isVisibleToUser;
}
/**
* @return The current value of the user-visible hint on this fragment.
* @see #setUserVisibleHint(boolean)
*/
public boolean getUserVisibleHint() {
return mUserVisibleHint;
}
/**
* Return the LoaderManager for this fragment, creating it if needed.
*/
@ -1263,7 +1299,8 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
writer.print(" mMenuVisible="); writer.print(mMenuVisible);
writer.print(" mHasMenu="); writer.println(mHasMenu);
writer.print(prefix); writer.print("mRetainInstance="); writer.print(mRetainInstance);
writer.print(" mRetaining="); writer.println(mRetaining);
writer.print(" mRetaining="); writer.print(mRetaining);
writer.print(" mUserVisibleHint="); writer.println(mUserVisibleHint);
if (mFragmentManager != null) {
writer.print(prefix); writer.print("mFragmentManager=");
writer.println(mFragmentManager);

@ -21,20 +21,11 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.app.ActionBarWrapper;
import com.actionbarsherlock.internal.app.ActionBarImpl;
import com.actionbarsherlock.internal.view.menu.MenuBuilder;
import com.actionbarsherlock.internal.view.menu.MenuInflaterWrapper;
import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
import com.actionbarsherlock.internal.view.menu.MenuWrapper;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@ -42,19 +33,36 @@ import android.os.Message;
import android.os.Parcelable;
import android.support.v4.view.ActionMode;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuInflater;
import android.support.v4.view.MenuItem;
import android.support.v4.view.Window;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.app.ActionBarImpl;
import com.actionbarsherlock.internal.app.ActionBarWrapper;
import com.actionbarsherlock.internal.view.menu.MenuBuilder;
import com.actionbarsherlock.internal.view.menu.MenuInflaterImpl;
import com.actionbarsherlock.internal.view.menu.MenuInflaterWrapper;
import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
import com.actionbarsherlock.internal.view.menu.MenuPresenter;
import com.actionbarsherlock.internal.view.menu.MenuWrapper;
import com.actionbarsherlock.internal.widget.ActionBarView;
/**
* Base class for activities that want to use the support-based ActionBar,
* Fragment, and Loader APIs.
* Base class for activities that want to use the support-based
* {@link android.support.v4.app.Fragment},
* {@link android.support.v4.content.Loader}, and
* {@link android.support.v4.app.ActionBar} APIs.
*
* <p>When using this class as opposed to new platform's built-in fragment
* and loader support, you must use the {@link #getSupportFragmentManager()}
* and {@link #getSupportLoaderManager()} methods respectively to access
* those features.
*
* <p>Known limitations:</p>
* <ul>
@ -69,7 +77,7 @@ import android.widget.FrameLayout;
* prior to Honeycomb, where the state is saved before pausing. To address this,
* when running on platforms prior to Honeycomb an exception will not be thrown
* if you change fragments between the state save and the activity being stopped.
* This means that is some cases if the activity is restored from its last saved
* This means that in some cases if the activity is restored from its last saved
* state, this may be a snapshot slightly before what the user last saw.</p>
* </ul>
*/
@ -84,12 +92,6 @@ public class FragmentActivity extends Activity implements SupportActivity {
static final int MSG_REALLY_STOPPED = 1;
static final int MSG_RESUME_PENDING = 2;
private static final int WINDOW_FLAG_ACTION_BAR = 1 << Window.FEATURE_ACTION_BAR;
private static final int WINDOW_FLAG_ACTION_BAR_ITEM_TEXT = 1 << Window.FEATURE_ACTION_BAR_ITEM_TEXT;
private static final int WINDOW_FLAG_ACTION_BAR_OVERLAY = 1 << Window.FEATURE_ACTION_BAR_OVERLAY;
private static final int WINDOW_FLAG_ACTION_MODE_OVERLAY = 1 << Window.FEATURE_ACTION_MODE_OVERLAY;
private static final int WINDOW_FLAG_INDETERMINANTE_PROGRESS = 1 << Window.FEATURE_INDETERMINATE_PROGRESS;
final SupportActivity.InternalCallbacks mInternalCallbacks = new SupportActivity.InternalCallbacks() {
@Override
void invalidateSupportFragmentIndex(int index) {
@ -111,11 +113,6 @@ public class FragmentActivity extends Activity implements SupportActivity {
return mFragments;
}
@Override
void ensureSupportActionBarAttached() {
FragmentActivity.this.ensureSupportActionBarAttached();
}
@Override
boolean getRetaining() {
return mRetaining;
@ -142,16 +139,54 @@ public class FragmentActivity extends Activity implements SupportActivity {
};
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
ViewGroup mDecor;
ViewGroup mContentParent;
ActionBar mActionBar;
boolean mIsActionBarImplAttached;
ActionBarView mActionBarView;
long mWindowFlags = 0;
final MenuBuilder mSupportMenu;
android.view.MenuInflater mMenuInflater;
MenuBuilder mSupportMenu;
final MenuBuilder.Callback mSupportMenuCallback = new MenuBuilder.Callback() {
@Override
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
return FragmentActivity.this.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
}
@Override
public void onMenuModeChange(MenuBuilder menu) {
// No-op
}
};
private final MenuPresenter.Callback mMenuPresenterCallback = new MenuPresenter.Callback() {
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
return false;
}
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
}
};
/** Map between native options items and sherlock items (pre-3.0 only). */
private HashMap<android.view.MenuItem, MenuItemImpl> mNativeItemMap;
/** Native menu item callback which proxies to our callback. */
private final android.view.MenuItem.OnMenuItemClickListener mNativeItemListener = new android.view.MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(android.view.MenuItem item) {
if (DEBUG) Log.d(TAG, "[mNativeItemListener.onMenuItemClick] item: " + item);
final MenuItemImpl sherlockItem = mNativeItemMap.get(item);
if (sherlockItem != null) {
sherlockItem.invoke();
} else {
Log.e(TAG, "Options item \"" + item + "\" not found in mapping");
}
return true; //Do not allow continuation of native handling
}
};
boolean mCreated;
@ -187,15 +222,7 @@ public class FragmentActivity extends Activity implements SupportActivity {
public FragmentActivity() {
if (IS_HONEYCOMB) {
mActionBar = ActionBarWrapper.createFor(this);
mSupportMenu = null; //Everything should be done natively
} else {
mSupportMenu = new MenuBuilder(this);
mSupportMenu.setCallback(mSupportMenuCallback);
}
}
@Override
public SupportActivity.InternalCallbacks getInternalCallbacks() {
@ -207,39 +234,124 @@ public class FragmentActivity extends Activity implements SupportActivity {
return this;
}
protected void ensureSupportActionBarAttached() {
if (IS_HONEYCOMB || mIsActionBarImplAttached) {
private void initActionBar() {
if (DEBUG) Log.d(TAG, "[initActionBar]");
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
if (mDecor == null) {
installDecor();
}
if ((mActionBar != null) || !hasFeature(Window.FEATURE_ACTION_BAR) || isChild()) {
return;
}
if (!isChild()) {
if ((mWindowFlags & WINDOW_FLAG_ACTION_BAR) == WINDOW_FLAG_ACTION_BAR) {
if ((mWindowFlags & WINDOW_FLAG_ACTION_BAR_OVERLAY) == WINDOW_FLAG_ACTION_BAR_OVERLAY) {
super.setContentView(R.layout.abs__screen_action_bar_overlay);
} else {
super.setContentView(R.layout.abs__screen_action_bar);
}
mActionBar = new ActionBarImpl(this);
((ActionBarImpl)mActionBar).init();
if (IS_HONEYCOMB) {
mActionBar = ActionBarWrapper.createFor(this);
} else {
mActionBar = new ActionBarImpl(this);
}
}
final boolean textEnabled = ((mWindowFlags & WINDOW_FLAG_ACTION_BAR_ITEM_TEXT) == WINDOW_FLAG_ACTION_BAR_ITEM_TEXT);
mSupportMenu.setShowsActionItemText(textEnabled);
private void installDecor() {
if (DEBUG) Log.d(TAG, "[installDecor]");
if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
((ActionBarImpl)mActionBar).setProgressBarIndeterminateVisibility(false);
if (mDecor == null) {
if (IS_HONEYCOMB) {
mDecor = (ViewGroup)getWindow().getDecorView();
} else {
mDecor = (ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content);
}
}
if (mContentParent == null) {
if (IS_HONEYCOMB) {
mContentParent = (ViewGroup)mDecor.findViewById(android.R.id.content);
} else {
mContentParent = generateLayout();
mActionBarView = (ActionBarView)mDecor.findViewById(R.id.abs__action_bar);
if (mActionBarView != null) {
if (mActionBarView.getTitle() == null) {
mActionBarView.setTitle(getTitle());
}
if (hasFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) {
mActionBarView.initIndeterminateProgress();
}
}
//TODO set other flags
// Post the panel invalidate for later; avoid application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
mDecor.post(new Runnable() {
@Override
public void run() {
//Invalidate if the panel menu hasn't been created before this.
if (mSupportMenu == null) {
invalidateOptionsMenu();
}
}
});
}
}
}
private ViewGroup generateLayout() {
if (DEBUG) Log.d(TAG, "[generateLayout]");
// Apply data from current theme.
TypedArray a = getTheme().obtainStyledAttributes(R.styleable.SherlockTheme);
if (a.getBoolean(R.styleable.SherlockTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.SherlockTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(Window.FEATURE_ACTION_BAR);
}
if (a.getBoolean(R.styleable.SherlockTheme_windowActionBarOverlay, false)) {
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false)) {
requestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
}
a.recycle();
int layoutResource;
if (hasFeature(Window.FEATURE_ACTION_BAR)) {
if (hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
layoutResource = R.layout.abs__screen_action_bar_overlay;
} else {
if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
super.requestWindowFeature((int)Window.FEATURE_INDETERMINATE_PROGRESS);
}
super.setContentView(R.layout.abs__screen_simple);
layoutResource = R.layout.abs__screen_action_bar;
}
//} else if (hasFeature(Window.FEATURE_ACTION_MODE_OVERLAY)) {
// layoutResource = R.layout.abs__screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.abs__screen_simple;
}
invalidateOptionsMenu();
mIsActionBarImplAttached = true;
View in = getLayoutInflater().inflate(layoutResource, null);
mDecor.addView(in, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)mDecor.findViewById(R.id.abs__content);
if (contentParent == null) {
throw new RuntimeException("Couldn't find content container view");
}
//Make our new child the true content view (for fragments). VERY VOLATILE!
mDecor.setId(View.NO_ID);
contentParent.setId(android.R.id.content);
return contentParent;
}
private boolean hasFeature(long featureId) {
if (IS_HONEYCOMB) {
return getWindow().hasFeature((int)featureId);
}
return (mWindowFlags & (1 << featureId)) != 0;
}
// ------------------------------------------------------------------------
@ -259,12 +371,11 @@ public class FragmentActivity extends Activity implements SupportActivity {
if (!IS_HONEYCOMB) {
switch ((int)featureId) {
case (int)Window.FEATURE_ACTION_BAR:
case (int)Window.FEATURE_ACTION_BAR_ITEM_TEXT:
case (int)Window.FEATURE_ACTION_BAR_OVERLAY:
case (int)Window.FEATURE_ACTION_MODE_OVERLAY:
case (int)Window.FEATURE_INDETERMINATE_PROGRESS:
mWindowFlags |= (1 << featureId);
return true;
return true;
}
}
return super.requestWindowFeature((int)featureId);
@ -272,58 +383,86 @@ public class FragmentActivity extends Activity implements SupportActivity {
@Override
public android.view.MenuInflater getMenuInflater() {
if (DEBUG) Log.d(TAG, "[getMenuInflater]");
if (mMenuInflater == null) {
initActionBar();
}
if (IS_HONEYCOMB) {
if (DEBUG) Log.d(TAG, "getMenuInflater(): Wrapping native inflater.");
//Wrap the native inflater so it can unwrap the native menu first
return new MenuInflaterWrapper(this, super.getMenuInflater());
}
mMenuInflater = new MenuInflaterWrapper(this, super.getMenuInflater());
} else {
if (DEBUG) Log.d(TAG, "getMenuInflater(): Returning support inflater.");
if (DEBUG) Log.d(TAG, "getMenuInflater(): Returning support inflater.");
//Use our custom menu inflater
mMenuInflater = new MenuInflaterImpl(this, super.getMenuInflater());
}
//Use our custom menu inflater
return new MenuInflater(this, super.getMenuInflater());
return mMenuInflater;
}
@Override
public void setContentView(int layoutResId) {
ensureSupportActionBarAttached();
if (IS_HONEYCOMB || isChild()) {
super.setContentView(layoutResId);
if (DEBUG) Log.d(TAG, "[setContentView] layoutResId: " + layoutResId);
if (mContentParent == null) {
installDecor();
} else {
FrameLayout contentView = (FrameLayout)findViewById(R.id.abs__content);
contentView.removeAllViews();
getLayoutInflater().inflate(layoutResId, contentView, true);
mContentParent.removeAllViews();
}
getLayoutInflater().inflate(layoutResId, mContentParent);
android.view.Window.Callback callback = getWindow().getCallback();
if (callback != null) {
callback.onContentChanged();
}
initActionBar();
}
@Override
public void setContentView(View view, LayoutParams params) {
ensureSupportActionBarAttached();
if (IS_HONEYCOMB || isChild()) {
super.setContentView(view, params);
if (DEBUG) Log.d(TAG, "[setContentView] view: " + view + ", params: " + params);
if (mContentParent == null) {
installDecor();
} else {
FrameLayout contentView = (FrameLayout)findViewById(R.id.abs__content);
contentView.removeAllViews();
contentView.addView(view, params);
mContentParent.removeAllViews();
}
mContentParent.addView(view, params);
android.view.Window.Callback callback = getWindow().getCallback();
if (callback != null) {
callback.onContentChanged();
}
initActionBar();
}
@Override
public void setContentView(View view) {
ensureSupportActionBarAttached();
if (IS_HONEYCOMB || isChild()) {
super.setContentView(view);
} else {
FrameLayout contentView = (FrameLayout)findViewById(R.id.abs__content);
contentView.removeAllViews();
contentView.addView(view);
if (DEBUG) Log.d(TAG, "[setContentView] view: " + view);
setContentView(view, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
if (DEBUG) Log.d(TAG, "[addContentView] view: " + view + ", params: " + params);
if (mContentParent == null) {
installDecor();
}
mContentParent.addView(view, params);
initActionBar();
}
@Override
public void setTitle(CharSequence title) {
if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
if (IS_HONEYCOMB || (mActionBar == null)) {
super.setTitle(title);
} else {
getSupportActionBar().setTitle(title);
@ -332,7 +471,7 @@ public class FragmentActivity extends Activity implements SupportActivity {
@Override
public void setTitle(int titleId) {
if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
if (IS_HONEYCOMB || (mActionBar == null)) {
super.setTitle(titleId);
} else {
getSupportActionBar().setTitle(titleId);
@ -365,20 +504,6 @@ public class FragmentActivity extends Activity implements SupportActivity {
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
TypedArray attrs = theme.obtainStyledAttributes(resid, R.styleable.SherlockTheme);
final boolean actionBar = attrs.getBoolean(R.styleable.SherlockTheme_windowActionBar, false);
mWindowFlags |= actionBar ? WINDOW_FLAG_ACTION_BAR : 0;
final boolean actionModeOverlay = attrs.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false);
mWindowFlags |= actionModeOverlay ? WINDOW_FLAG_ACTION_MODE_OVERLAY : 0;
attrs.recycle();
super.onApplyThemeResource(theme, resid, first);
}
/**
* Take care of popping the fragment back stack or finishing the activity
* as appropriate.
@ -448,8 +573,8 @@ public class FragmentActivity extends Activity implements SupportActivity {
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu(Menu): Returning " + menu.hasVisibleItems());
return menu.hasVisibleItems();
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu(Menu): Returning true");
return true;
}
@Override
@ -470,6 +595,14 @@ public class FragmentActivity extends Activity implements SupportActivity {
return result;
}
private boolean dispatchCreateOptionsMenu() {
if (DEBUG) Log.d(TAG, "[dispatchCreateOptionsMenu]");
boolean result = onCreateOptionsMenu(mSupportMenu);
result |= mFragments.dispatchCreateOptionsMenu(mSupportMenu, getMenuInflater());
return result;
}
/**
* Add support for inflating the &lt;fragment> tag.
*/
@ -555,29 +688,40 @@ public class FragmentActivity extends Activity implements SupportActivity {
@Override
public void invalidateOptionsMenu() {
if (DEBUG) Log.d(TAG, "supportInvalidateOptionsMenu(): Invalidating menu.");
if (DEBUG) Log.d(TAG, "[invalidateOptionsMenu]");
if (IS_HONEYCOMB) {
HoneycombInvalidateOptionsMenu.invoke(this);
} else {
mSupportMenu.clear();
return;
}
mOptionsMenuCreateResult = onCreateOptionsMenu(mSupportMenu);
mOptionsMenuCreateResult |= mFragments.dispatchCreateOptionsMenu(mSupportMenu, getMenuInflater());
if (mSupportMenu == null) {
mSupportMenu = new MenuBuilder(this);
mSupportMenu.setCallback(mSupportMenuCallback);
}
if (getSupportActionBar() != null) {
if (onPrepareOptionsMenu(mSupportMenu)) {
mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
}
mSupportMenu.stopDispatchingItemsChanged();
mSupportMenu.clear();
//Since we now know we are using a custom action bar, perform the
//inflation callback to allow it to display any items it wants.
((ActionBarImpl)mActionBar).onMenuInflated(mSupportMenu);
if (!dispatchCreateOptionsMenu()) {
if (mActionBar != null) {
((ActionBarImpl)mActionBar).setMenu(null, mMenuPresenterCallback);
}
return;
}
if (!dispatchPrepareOptionsMenu()) {
if (mActionBar != null) {
((ActionBarImpl)mActionBar).setMenu(null, mMenuPresenterCallback);
}
mSupportMenu.startDispatchingItemsChanged();
return;
}
mSupportMenu.startDispatchingItemsChanged();
// Whoops, older platform... we'll use a hack, to manually rebuild
// the options menu the next time it is prepared.
mOptionsMenuInvalidated = true;
if (mActionBar != null) {
((ActionBarImpl)mActionBar).setMenu(mSupportMenu, mMenuPresenterCallback);
}
}
@ -733,68 +877,46 @@ public class FragmentActivity extends Activity implements SupportActivity {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean result = menu.hasVisibleItems();
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(Menu): Returning " + result);
return result;
return true;
}
@Override
public final boolean onPrepareOptionsMenu(android.view.Menu menu) {
boolean result = super.onPrepareOptionsMenu(menu);
if (!IS_HONEYCOMB) {
if (DEBUG) {
Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): mOptionsMenuCreateResult = " + mOptionsMenuCreateResult);
Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): mOptionsMenuInvalidated = " + mOptionsMenuInvalidated);
}
boolean prepareResult = true;
if (mOptionsMenuCreateResult) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Calling support method with custom menu.");
prepareResult = onPrepareOptionsMenu(mSupportMenu);
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Support method result returned " + prepareResult);
if (prepareResult) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatching fragment method with custom menu.");
mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
}
}
if (mOptionsMenuInvalidated) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Clearing existing options menu.");
menu.clear();
mOptionsMenuInvalidated = false;
if (mOptionsMenuCreateResult && prepareResult) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Adding any action items that are not displayed on the action bar.");
//Only add items that have not already been added to our custom
//action bar implementation
for (MenuItemImpl item : mSupportMenu.getItems()) {
if (!item.isShownOnActionBar()) {
item.addTo(menu);
}
}
}
}
if (mOptionsMenuCreateResult && prepareResult && menu.hasVisibleItems()) {
if (getSupportActionBar() != null) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatch menu visibility true to custom action bar.");
((ActionBarImpl)mActionBar).onMenuVisibilityChanged(true);
}
result = true;
}
} else {
if (IS_HONEYCOMB) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Calling support method with wrapped native menu.");
final MenuWrapper wrappedMenu = new MenuWrapper(menu);
result = onPrepareOptionsMenu(wrappedMenu);
boolean result = onPrepareOptionsMenu(wrappedMenu);
if (result) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatching fragment method with wrapped native menu.");
mFragments.dispatchPrepareOptionsMenu(wrappedMenu);
}
return result;
}
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Returning " + result);
return result;
if (!dispatchPrepareOptionsMenu()) {
return false;
}
if (mNativeItemMap == null) {
mNativeItemMap = new HashMap<android.view.MenuItem, MenuItemImpl>();
} else {
mNativeItemMap.clear();
}
if (mSupportMenu != null) {
return mSupportMenu.bindOverflowToNative(menu, mNativeItemListener, mNativeItemMap);
}
return false;
}
private boolean dispatchPrepareOptionsMenu() {
if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu]");
if (onPrepareOptionsMenu(mSupportMenu)) {
mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
return true;
}
return false;
}
/**
@ -901,7 +1023,6 @@ public class FragmentActivity extends Activity implements SupportActivity {
if (!mCreated) {
mCreated = true;
ensureSupportActionBarAttached(); //Needed for retained fragments
mFragments.dispatchActivityCreated();
}
@ -958,10 +1079,10 @@ public class FragmentActivity extends Activity implements SupportActivity {
*/
@Override
public void setProgressBarIndeterminateVisibility(Boolean visible) {
if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
if (IS_HONEYCOMB || (mActionBar == null)) {
super.setProgressBarIndeterminateVisibility(visible);
} else if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
((ActionBarImpl)mActionBar).setProgressBarIndeterminateVisibility(visible);
} else {
mActionBarView.setProgressBarIndeterminateVisibility(visible);
}
}
@ -1073,7 +1194,8 @@ public class FragmentActivity extends Activity implements SupportActivity {
*/
@Override
public ActionBar getSupportActionBar() {
return (mActionBar != null) ? mActionBar.getPublicInstance() : null;
initActionBar();
return mActionBar;
}
/**
@ -1160,6 +1282,8 @@ public class FragmentActivity extends Activity implements SupportActivity {
*/
@Override
public FragmentManager getSupportFragmentManager() {
//PLEASE let no one be dumb enough to call this too soon...
initActionBar();
return mFragments;
}

@ -41,7 +41,6 @@ import android.view.animation.Animation.AnimationListener;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import com.actionbarsherlock.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@ -384,6 +383,7 @@ final class FragmentManagerImpl extends FragmentManager {
static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state";
static final String TARGET_STATE_TAG = "android:target_state";
static final String VIEW_STATE_TAG = "android:view_state";
static final String USER_VISIBLE_HINT_TAG = "android:user_visible_hint";
ArrayList<Runnable> mPendingActions;
Runnable[] mTmpActions;
@ -408,6 +408,7 @@ final class FragmentManagerImpl extends FragmentManager {
boolean mStateSaved;
boolean mDestroyed;
String mNoTransactionsBecause;
boolean mHavePendingDeferredStart;
// Temporary vars for state save and restore.
Bundle mStateBundle = null;
@ -756,6 +757,18 @@ final class FragmentManagerImpl extends FragmentManager {
return null;
}
public void performPendingDeferredStart(Fragment f) {
if (f.mDeferStart) {
if (mExecutingActions) {
// Wait until we're done executing our pending transactions
mHavePendingDeferredStart = true;
return;
}
f.mDeferStart = false;
moveToState(f, mCurState, 0, 0);
}
}
void moveToState(Fragment f, int newState, int transit, int transitionStyle) {
// Fragments that are not currently added will sit in the onCreate() state.
if (!f.mAdded && newState > Fragment.CREATED) {
@ -765,7 +778,11 @@ final class FragmentManagerImpl extends FragmentManager {
// While removing a fragment, we can't change it to a higher state.
newState = f.mState;
}
// Defer start if requested; don't allow it to move to STARTED or higher
// if it's not already started.
if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}
if (f.mState < newState) {
// For fragments that are created from a layout, when restoring from
// state we don't want to allow them to be created until they are
@ -793,6 +810,14 @@ final class FragmentManagerImpl extends FragmentManager {
f.mTargetRequestCode = f.mSavedFragmentState.getInt(
FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
}
f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
if (!f.mUserVisibleHint) {
f.mDeferStart = true;
if (newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}
}
}
f.mActivity = mActivity;
f.mFragmentManager = mActivity.getInternalCallbacks().getFragments();
@ -1048,13 +1073,21 @@ final class FragmentManagerImpl extends FragmentManager {
mCurState = newState;
if (mActive != null) {
boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
moveToState(f, newState, transit, transitStyle);
if (f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
}
if (!loadersRunning) {
startPendingDeferredFragments();
}
if (mNeedMenuInvalidate && mActivity != null && mCurState == Fragment.RESUMED) {
mActivity.invalidateOptionsMenu();
mNeedMenuInvalidate = false;
@ -1062,6 +1095,17 @@ final class FragmentManagerImpl extends FragmentManager {
}
}
void startPendingDeferredFragments() {
if (mActive == null) return;
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
performPendingDeferredStart(f);
}
}
}
void makeActive(Fragment f) {
if (f.mIndex >= 0) {
return;
@ -1199,12 +1243,6 @@ final class FragmentManagerImpl extends FragmentManager {
}
public Fragment findFragmentById(int id) {
if (!HONEYCOMB && (id == android.R.id.content)) {
// android.R.id.content would point to the entire content area,
// including the custom action bar
id = R.id.abs__content;
}
if (mActive != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
@ -1275,7 +1313,6 @@ final class FragmentManagerImpl extends FragmentManager {
if (mActivity == null) {
throw new IllegalStateException("Activity has been destroyed");
}
mActivity.getInternalCallbacks().ensureSupportActionBarAttached();
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
@ -1362,7 +1399,7 @@ final class FragmentManagerImpl extends FragmentManager {
synchronized (this) {
if (mPendingActions == null || mPendingActions.size() == 0) {
return didSomething;
break;
}
numActions = mPendingActions.size();
@ -1382,6 +1419,21 @@ final class FragmentManagerImpl extends FragmentManager {
mExecutingActions = false;
didSomething = true;
}
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
if (!loadersRunning) {
mHavePendingDeferredStart = false;
startPendingDeferredFragments();
}
}
return didSomething;
}
void reportBackStackChanged() {
@ -1519,6 +1571,10 @@ final class FragmentManagerImpl extends FragmentManager {
result.putSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
}
if (!f.mUserVisibleHint) {
// Only add this if it's not the default value
result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
}
return result;
}

@ -20,11 +20,43 @@ import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
/**
* Implementation of {@link android.support.v4.view.PagerAdapter} that
* represents each page as a {@link Fragment} that is persistently kept in the
* fragment manager as long as the user can return to the page.
* represents each page as a {@link Fragment} that is persistently
* kept in the fragment manager as long as the user can return to the page.
*
* <p>This version of the pager is best for use when there are a handful of
* typically more static fragments to be paged through, such as a set of tabs.
* The fragment of each page the user visits will be kept in memory, though its
* view hierarchy may be destroyed when not visible. This can result in using
* a significant amount of memory since fragment instances can hold on to an
* arbitrary amount of state. For larger sets of pages, consider
* {@link FragmentStatePagerAdapter}.
*
* <p>When using FragmentPagerAdapter the host ViewPager must have a
* valid ID set.</p>
*
* <p>Subclasses only need to implement {@link #getItem(int)}
* and {@link #getCount()} to have a working adapter.
*
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
* {@sample development/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentPagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
* {@sample development/samples/Support4Demos/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
* {@sample development/samples/Support4Demos/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
@ -44,11 +76,11 @@ public abstract class FragmentPagerAdapter extends PagerAdapter {
public abstract Fragment getItem(int position);
@Override
public void startUpdate(View container) {
public void startUpdate(ViewGroup container) {
}
@Override
public Object instantiateItem(View container, int position) {
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
@ -57,48 +89,50 @@ public abstract class FragmentPagerAdapter extends PagerAdapter {
String name = makeFragmentName(container.getId(), position);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG)
Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG)
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), position));
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), position));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
public void destroyItem(View container, int position, Object object) {
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG)
Log.v(TAG, "Detaching item #" + position + ": f=" + object + " v=" + ((Fragment) object).getView());
mCurTransaction.detach((Fragment) object);
if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
@Override
public void setPrimaryItem(View container, int position, Object object) {
Fragment fragment = (Fragment) object;
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(View container) {
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
@ -108,7 +142,7 @@ public abstract class FragmentPagerAdapter extends PagerAdapter {
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
return ((Fragment)object).getView() == view;
}
@Override

@ -23,7 +23,44 @@ import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
/**
* Implementation of {@link android.support.v4.view.PagerAdapter} that
* uses a {@link Fragment} to manage each page. This class also handles
* saving and restoring of fragment's state.
*
* <p>This version of the pager is more useful when there are a large number
* of pages, working more like a list view. When pages are not visible to
* the user, their entire fragment may be destroyed, only keeping the saved
* state of that fragment. This allows the pager to hold on to much less
* memory associated with each visited page as compared to
* {@link FragmentPagerAdapter} at the cost of potentially more overhead when
* switching between pages.
*
* <p>When using FragmentPagerAdapter the host ViewPager must have a
* valid ID set.</p>
*
* <p>Subclasses only need to implement {@link #getItem(int)}
* and {@link #getCount()} to have a working adapter.
*
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
* {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
* {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private static final boolean DEBUG = false;
@ -45,11 +82,11 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
public abstract Fragment getItem(int position);
@Override
public void startUpdate(View container) {
public void startUpdate(ViewGroup container) {
}
@Override
public Object instantiateItem(View container, int position) {
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
@ -84,7 +121,7 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
}
@Override
public void destroyItem(View container, int position, Object object) {
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (mCurTransaction == null) {
@ -102,7 +139,7 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
}
@Override
public void setPrimaryItem(View container, int position, Object object) {
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
@ -116,7 +153,7 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
}
@Override
public void finishUpdate(View container) {
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;

@ -215,7 +215,7 @@ public abstract class FragmentTransaction {
/**
* Set the full title to show as a bread crumb when this transaction
* is on the back stack, as used by {@link FragmentBreadCrumbs}.
* is on the back stack.
*
* @param res A string resource containing the title.
*/
@ -230,7 +230,7 @@ public abstract class FragmentTransaction {
/**
* Set the short title to show as a bread crumb when this transaction
* is on the back stack, as used by {@link FragmentBreadCrumbs}.
* is on the back stack.
*
* @param res A string resource containing the title.
*/

@ -17,7 +17,8 @@
package android.support.v4.app;
/**
* A copy of Honeycomb's SparseArray, only so we can have the removeAt() method.
* A copy of Honeycomb's {@link android.util.SparseArray}, that
* provides a removeAt() method.
*/
public class HCSparseArray<E> {
private static final Object DELETED = new Object();
@ -330,18 +331,6 @@ public class HCSparseArray<E> {
return ~high;
}
/*private void checkIntegrity() {
for (int i = 1; i < mSize; i++) {
if (mKeys[i] <= mKeys[i - 1]) {
for (int j = 0; j < mSize; j++) {
Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
}
throw new RuntimeException();
}
}
}*/
static int idealByteArraySize(int need) {
for (int i = 4; i < 32; i++)
if (need <= (1 << i) - 12)

@ -113,13 +113,13 @@ public class ListFragment extends Fragment {
FrameLayout lframe = new FrameLayout(context);
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
TextView tv = new TextView(context);
TextView tv = new TextView(getActivity());
tv.setId(INTERNAL_EMPTY_ID);
tv.setGravity(Gravity.CENTER);
lframe.addView(tv, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
ListView lv = new ListView(context);
ListView lv = new ListView(getActivity());
lv.setId(android.R.id.list);
lv.setDrawSelectorOnTop(false);
lframe.addView(lv, new FrameLayout.LayoutParams(

@ -174,6 +174,12 @@ public abstract class LoaderManager {
public static void enableDebugLogging(boolean enabled) {
LoaderManagerImpl.DEBUG = enabled;
}
/**
* Returns true if any loaders managed are currently running and have not
* returned data to the application yet.
*/
public boolean hasRunningLoaders() { return false; }
}
class LoaderManagerImpl extends LoaderManager {
@ -398,6 +404,10 @@ class LoaderManagerImpl extends LoaderManager {
info.destroy();
mInactiveLoaders.remove(mId);
}
if (mActivity != null && !hasRunningLoaders()) {
mActivity.getInternalCallbacks().getFragments().startPendingDeferredFragments();
}
}
void callOnLoadFinished(Loader<Object> loader, Object data) {
@ -657,6 +667,9 @@ class LoaderManagerImpl extends LoaderManager {
mInactiveLoaders.removeAt(idx);
info.destroy();
}
if (mActivity != null && !hasRunningLoaders()) {
mActivity.getInternalCallbacks().getFragments().startPendingDeferredFragments();
}
}
/**
@ -800,4 +813,15 @@ class LoaderManagerImpl extends LoaderManager {
}
}
}
@Override
public boolean hasRunningLoaders() {
boolean loadersRunning = false;
final int count = mLoaders.size();
for (int i = 0; i < count; i++) {
final LoaderInfo li = mLoaders.valueAt(i);
loadersRunning |= li.mStarted && !li.mDeliveredData;
}
return loadersRunning;
}
}

@ -24,7 +24,7 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
/**
* Pre-Honeycomb versions of the platform don't have View.setSaveFromParentEnabled(),
* Pre-Honeycomb versions of the platform don't have {@link View#setSaveFromParentEnabled(boolean)},
* so instead we insert this between the view and its parent.
*/
public class NoSaveStateFrameLayout extends FrameLayout {

@ -0,0 +1,45 @@
/*
* 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 android.support.v4.app;
/**
* Helper for accessing features in {@link android.app.Service}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class ServiceCompat {
private ServiceCompat() {
/* Hide constructor */
}
/**
* Constant to return from {@link android.app.Service#onStartCommand}: if this
* service's process is killed while it is started (after returning from
* {@link android.app.Service#onStartCommand}), then leave it in the started
* state but don't retain this delivered intent. Later the system will try to
* re-create the service. Because it is in the started state, it will
* guarantee to call {@link android.app.Service#onStartCommand} after creating
* the new service instance; if there are not any pending start commands to be
* delivered to the service, it will be called with a null intent
* object, so you must take care to check for this.
*
* <p>This mode makes sense for things that will be explicitly started
* and stopped to run for arbitrary periods of time, such as a service
* performing background music playback.
*/
public static final int START_STICKY = 1;
}

@ -64,7 +64,6 @@ import android.view.accessibility.AccessibilityEvent;
*/
public interface SupportActivity extends SherlockActivity {
public static abstract class InternalCallbacks {
abstract void ensureSupportActionBarAttached();
abstract Handler getHandler();
abstract FragmentManagerImpl getFragments();
abstract LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create);

@ -116,7 +116,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
}
/**
* Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
* Attempt to cancel the current load task. See {@link android.os.AsyncTask#cancel(boolean)}
* for more info. Must be called on the main thread of the process.
*
* <p>Cancelling is not an immediate operation, since the load is performed

@ -98,7 +98,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
/**
* Creates a fully-specified CursorLoader. See
* {@link ContentResolver#query(Uri, String[], String, String[], String)
* {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
* ContentResolver.query()} for documentation on the meaning of the
* parameters. These will be passed as-is to that call.
*/

@ -0,0 +1,96 @@
/*
* 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 android.support.v4.content;
/**
* Helper for accessing features in {@link android.content.Intent}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class IntentCompat {
private IntentCompat() {
/* Hide constructor */
}
/**
* Broadcast Action: Resources for a set of packages (which were
* previously unavailable) are currently
* available since the media on which they exist is available.
* The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
* list of packages whose availability changed.
* The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
* list of uids of packages whose availability changed.
* Note that the
* packages in this list do <em>not</em> receive this broadcast.
* The specified set of packages are now available on the system.
* <p>Includes the following extras:
* <ul>
* <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
* whose resources(were previously unavailable) are currently available.
* {@link #EXTRA_CHANGED_UID_LIST} is the set of uids of the
* packages whose resources(were previously unavailable)
* are currently available.
* </ul>
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
public static final String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE =
"android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
/**
* Broadcast Action: Resources for a set of packages are currently
* unavailable since the media on which they exist is unavailable.
* The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
* list of packages whose availability changed.
* The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
* list of uids of packages whose availability changed.
* The specified set of packages can no longer be
* launched and are practically unavailable on the system.
* <p>Inclues the following extras:
* <ul>
* <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
* whose resources are no longer available.
* {@link #EXTRA_CHANGED_UID_LIST} is the set of packages
* whose resources are no longer available.
* </ul>
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
public static final String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE =
"android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
/**
* This field is part of
* {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
* {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
* and contains a string array of all of the components that have changed.
*/
public static final String EXTRA_CHANGED_PACKAGE_LIST =
"android.intent.extra.changed_package_list";
/**
* This field is part of
* {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
* {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
* and contains an integer array of uids of all of the components
* that have changed.
*/
public static final String EXTRA_CHANGED_UID_LIST =
"android.intent.extra.changed_uid_list";
}

@ -40,6 +40,13 @@ public class Loader<D> {
boolean mReset = true;
boolean mContentChanged = false;
/**
* An implementation of a ContentObserver that takes care of connecting
* it to the Loader to have the loader re-load its data when the observer
* is told it has changed. You do not normally need to use this yourself;
* it is used for you by {@link android.support.v4.content.CursorLoader}
* to take care of executing an update when the cursor's backing data changes.
*/
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
@ -56,6 +63,14 @@ public class Loader<D> {
}
}
/**
* Interface that is implemented to discover when a Loader has finished
* loading its data. You do not normally need to implement this yourself;
* it is used in the implementation of {@link android.support.v4.app.LoaderManager}
* to find out when a Loader it is managing has completed so that this can
* be reported to its client. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
*/
public interface OnLoadCompleteListener<D> {
/**
* Called on the thread that created the Loader when the load is complete.

@ -35,12 +35,16 @@ import android.os.Message;
import android.os.Process;
/**
* Copy of the required parts of AsyncTask from Android 3.0 that is needed
* to support AsyncTaskLoader. We use this rather than the one from the platform
* Copy of the required parts of {@link android.os.AsyncTask} from Android 3.0 that is
* needed to support AsyncTaskLoader. We use this rather than the one from the platform
* because we rely on some subtle behavior of AsyncTask that is not reliable on
* older platforms.
*
* <p>Note that for now this is not publicly available because it is not a
* complete implementation, only sufficient for the needs of
* {@link AsyncTaskLoader}.
*/
public abstract class ModernAsyncTask<Params, Progress, Result> {
abstract class ModernAsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
private static final int CORE_POOL_SIZE = 5;
@ -92,7 +96,7 @@ public abstract class ModernAsyncTask<Params, Progress, Result> {
*/
RUNNING,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
* Indicates that {@link android.os.AsyncTask#onPostExecute(Object)} has finished.
*/
FINISHED,
}
@ -358,7 +362,8 @@ public abstract class ModernAsyncTask<Params, Progress, Result> {
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
* {@link android.os.AsyncTask.Status#RUNNING} or
* {@link android.os.AsyncTask.Status#FINISHED}.
*/
public final ModernAsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
@ -380,9 +385,7 @@ public abstract class ModernAsyncTask<Params, Progress, Result> {
* there are no guarantees on the order of the modifications.
* Without careful work it is possible in rare cases for the newer version
* of the data to be over-written by an older one, leading to obscure data
* loss and stability issues. Such changes are best
* executed in serial; to guarantee such work is serialized regardless of
* platform version you can use this function with {@link #SERIAL_EXECUTOR}.
* loss and stability issues.
*
* <p>This method must be invoked on the UI thread.
*
@ -393,7 +396,8 @@ public abstract class ModernAsyncTask<Params, Progress, Result> {
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
* {@link android.os.AsyncTask.Status#RUNNING}
* or {@link android.os.AsyncTask.Status#FINISHED}.
*/
public final ModernAsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {

@ -0,0 +1,35 @@
/*
* 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 android.support.v4.content.pm;
/**
* Helper for accessing features in {@link android.content.pm.ActivityInfo}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class ActivityInfoCompat {
private ActivityInfoCompat() {
/* Hide constructor */
}
/**
* Bit in ActivityInfo#configChanges that indicates that the
* activity can itself handle the ui mode. Set from the
* {@link android.R.attr#configChanges} attribute.
*/
public static final int CONFIG_UI_MODE = 0x0200;
}

@ -0,0 +1,58 @@
/*
* 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 android.support.v4.database;
import android.text.TextUtils;
/**
* Helper for accessing features in {@link android.database.DatabaseUtils}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class DatabaseUtilsCompat {
private DatabaseUtilsCompat() {
/* Hide constructor */
}
/**
* Concatenates two SQL WHERE clauses, handling empty or null values.
*/
public static String concatenateWhere(String a, String b) {
if (TextUtils.isEmpty(a)) {
return b;
}
if (TextUtils.isEmpty(b)) {
return a;
}
return "(" + a + ") AND (" + b + ")";
}
/**
* Appends one set of selection args to another. This is useful when adding a selection
* argument to a user provided set.
*/
public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
if (originalValues == null || originalValues.length == 0) {
return newValues;
}
String[] result = new String[originalValues.length + newValues.length ];
System.arraycopy(originalValues, 0, result, 0, originalValues.length);
System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
return result;
}
}

@ -19,7 +19,18 @@ package android.support.v4.os;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Helper for accessing features in {@link android.os.Parcelable}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class ParcelableCompat {
/**
* Factory method for {@link Parcelable.Creator}.
*
* @param callbacks Creator callbacks implementation.
* @return New creator.
*/
public static <T> Parcelable.Creator<T> newCreator(
ParcelableCompatCreatorCallbacks<T> callbacks) {
if (android.os.Build.VERSION.SDK_INT >= 13) {

@ -18,7 +18,29 @@ package android.support.v4.os;
import android.os.Parcel;
/**
* Callbacks a {@link Parcelable} creator should implement.
*/
public interface ParcelableCompatCreatorCallbacks<T> {
/**
* Create a new instance of the Parcelable class, instantiating it
* from the given Parcel whose data had previously been written by
* {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
* using the given ClassLoader.
*
* @param in The Parcel to read the object's data from.
* @param loader The ClassLoader that this object is being created in.
* @return Returns a new instance of the Parcelable class.
*/
public T createFromParcel(Parcel in, ClassLoader loader);
/**
* Create a new array of the Parcelable class.
*
* @param size Size of the array.
* @return Returns an array of the Parcelable class, with every entry
* initialized to null.
*/
public T[] newArray(int size);
}

@ -17,9 +17,13 @@
package android.support.v4.util;
/**
* Useful debugging utilities that are not available on all versions of Android.
* Helper for accessing features in {@link android.util.DebugUtils}
* introduced after API level 4 in a backwards compatible fashion.
*
* @hide
*/
public class DebugUtils {
public static void buildShortClassTag(Object cls, StringBuilder out) {
if (cls == null) {
out.append("null");

@ -21,7 +21,10 @@ import android.util.Log;
import java.io.Writer;
/**
* Useful logging utility that is not available on all versions of Android.
* Helper for accessing features in {@link android.util.LogWriter}
* introduced after API level 4 in a backwards compatible fashion.
*
* @hide
*/
public class LogWriter extends Writer {
private final String mTag;
@ -31,12 +34,6 @@ public class LogWriter extends Writer {
* Create a new Writer that sends to the log with the given priority
* and tag.
*
* @param priority The desired log priority:
* {@link android.util.Log#VERBOSE Log.VERBOSE},
* {@link android.util.Log#DEBUG Log.DEBUG},
* {@link android.util.Log#INFO Log.INFO},
* {@link android.util.Log#WARN Log.WARN}, or
* {@link android.util.Log#ERROR Log.ERROR}.
* @param tag A string tag to associate with each printed log statement.
*/
public LogWriter(String tag) {

@ -20,7 +20,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
* Static library version of {@code android.util.LruCache}. Used to write apps
* Static library version of {@link android.util.LruCache}. Used to write apps
* that run on API levels prior to 12. When running on API level 12 or above,
* this implementation is still used; it does not try to switch to the
* framework's implementation. See the framework SDK documentation for a class

@ -19,7 +19,10 @@ package android.support.v4.util;
import java.io.PrintWriter;
/**
* Useful time utilities that are not available on all versions of Android.
* Helper for accessing features in {@link android.util.TimeUtils}
* introduced after API level 4 in a backwards compatible fashion.
*
* @hide
*/
public class TimeUtils {
/** @hide Field length that can hold 999 days of time */

@ -22,7 +22,8 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
* Helper for accessing AccessibilityDelegate from newer platform versions.
* Helper for accessing {@link View.AccessibilityDelegate} introduced after
* API level 4 in a backwards compatible fashion.
*/
public class AccessibilityDelegateCompat {
@ -33,7 +34,8 @@ public class AccessibilityDelegateCompat {
AccessibilityEvent event);
public void onInitializeAccessibilityEvent(Object delegate, View host,
AccessibilityEvent event);
public void onInitializeAccessibilityNodeInfo(Object delegate, View host, Object info);
public void onInitializeAccessibilityNodeInfo(Object delegate, View host,
AccessibilityNodeInfoCompat info);
public void onPopulateAccessibilityEvent(Object delegate, View host,
AccessibilityEvent event);
public boolean onRequestSendAccessibilityEvent(Object delegate, ViewGroup host, View child,
@ -62,7 +64,8 @@ public class AccessibilityDelegateCompat {
}
public void onInitializeAccessibilityNodeInfo(Object delegate, View host, Object info) {
public void onInitializeAccessibilityNodeInfo(Object delegate, View host,
AccessibilityNodeInfoCompat info) {
}
@ -150,8 +153,10 @@ public class AccessibilityDelegateCompat {
}
@Override
public void onInitializeAccessibilityNodeInfo(Object delegate, View host, Object info) {
AccessibilityDelegateCompatIcs.onInitializeAccessibilityNodeInfo(delegate, host, info);
public void onInitializeAccessibilityNodeInfo(Object delegate, View host,
AccessibilityNodeInfoCompat info) {
AccessibilityDelegateCompatIcs.onInitializeAccessibilityNodeInfo(delegate, host,
info.getImpl());
}
@Override

@ -16,6 +16,8 @@
package android.support.v4.view;
import com.actionbarsherlock.internal.view.menu.MenuInflaterImpl;
import android.view.View;
/**
@ -107,11 +109,11 @@ public abstract class ActionMode {
public abstract Menu getMenu();
/**
* Returns a {@link MenuInflater} with the ActionMode's context.
* Returns a {@link MenuInflaterImpl} with the ActionMode's context.
*
* @return Menu inflater.
*/
public abstract MenuInflater getMenuInflater();
public abstract MenuInflaterImpl getMenuInflater();
/**
* Returns the current subtitle of this action mode.

@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.KeyEvent;
/**
* Helper for accessing newer features in KeyEvent.
* Helper for accessing features in {@link KeyEvent} introduced after
* API level 4 in a backwards compatible fashion.
*/
public class KeyEventCompat {
/**

@ -1,6 +1,77 @@
package android.support.v4.view;
public interface Menu extends android.view.Menu {
/**
* This is the part of an order integer that the user can provide.
* @hide
*/
static final int USER_MASK = 0x0000ffff;
/**
* Bit shift of the user portion of the order integer.
* @hide
*/
static final int USER_SHIFT = 0;
/**
* This is the part of an order integer that supplies the category of the
* item.
* @hide
*/
static final int CATEGORY_MASK = 0xffff0000;
/**
* Bit shift of the category portion of the order integer.
* @hide
*/
static final int CATEGORY_SHIFT = 16;
/**
* Value to use for group and item identifier integers when you don't care
* about them.
*/
static final int NONE = 0;
/**
* First value for group and item identifier integers.
*/
static final int FIRST = 1;
// Implementation note: Keep these CATEGORY_* in sync with the category enum
// in attrs.xml
/**
* Category code for the order integer for items/groups that are part of a
* container -- or/add this with your base value.
*/
static final int CATEGORY_CONTAINER = 0x00010000;
/**
* Category code for the order integer for items/groups that are provided by
* the system -- or/add this with your base value.
*/
static final int CATEGORY_SYSTEM = 0x00020000;
/**
* Category code for the order integer for items/groups that are
* user-supplied secondary (infrequently used) options -- or/add this with
* your base value.
*/
static final int CATEGORY_SECONDARY = 0x00030000;
/**
* Category code for the order integer for items/groups that are
* alternative actions on the data that is currently displayed -- or/add
* this with your base value.
*/
static final int CATEGORY_ALTERNATIVE = 0x00040000;
/**
* Flag for {@link #addIntentOptions}: if set, do not automatically remove
* any existing menu items in the same group.
*/
static final int FLAG_APPEND_TO_GROUP = 0x0001;
@Override
MenuItem add(CharSequence title);

@ -0,0 +1,81 @@
/*
* 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 android.support.v4.view;
import android.view.MenuItem;
/**
* Use {@link FragmentActivity}, {@link Menu}, and {@link MenuItem}.
*/
@Deprecated
public class MenuCompat {
/**
* Interface for the full API.
*/
interface MenuVersionImpl {
public boolean setShowAsAction(MenuItem item, int actionEnum);
}
/**
* Interface implementation that doesn't use anything about v4 APIs.
*/
static class BaseMenuVersionImpl implements MenuVersionImpl {
@Override
public boolean setShowAsAction(MenuItem item, int actionEnum) {
return false;
}
}
/**
* Interface implementation for devices with at least v11 APIs.
*/
static class HoneycombMenuVersionImpl implements MenuVersionImpl {
@Override
public boolean setShowAsAction(MenuItem item, int actionEnum) {
MenuItemCompatHoneycomb.setShowAsAction(item, actionEnum);
return true;
}
}
/**
* Select the correct implementation to use for the current platform.
*/
static final MenuVersionImpl IMPL;
static {
if (android.os.Build.VERSION.SDK_INT >= 11) {
IMPL = new HoneycombMenuVersionImpl();
} else {
IMPL = new BaseMenuVersionImpl();
}
}
// -------------------------------------------------------------------
/**
* Call {@link MenuItem#setShowAsAction(int) MenuItem.setShowAsAction()}.
* If running on a pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} device,
* does nothing and returns false. Otherwise returns true.
*
* @deprecated Use {@link MenuItemCompat#setShowAsAction(MenuItem, int)
* MenuItemCompat.setShowAsAction(MenuItem, int)}
*/
@Deprecated
public static boolean setShowAsAction(MenuItem item, int actionEnum) {
return IMPL.setShowAsAction(item, actionEnum);
}
}

@ -0,0 +1,122 @@
/*
* 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 android.support.v4.view;
import android.view.MenuItem;
import android.view.View;
/**
* @Deprecated Use {@link FragmentActivity}, {@link Menu}, and {@link MenuItem}.
*/
@Deprecated
public class MenuItemCompat {
/**
* @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_NEVER}.
*/
@Deprecated
public static final int SHOW_AS_ACTION_NEVER = 0;
/**
* @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_IF_ROOM}.
*/
@Deprecated
public static final int SHOW_AS_ACTION_IF_ROOM = 1;
/**
* @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_ALWAYS}.
*/
@Deprecated
public static final int SHOW_AS_ACTION_ALWAYS = 2;
/**
* @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_WITH_TEXT}.
*/
@Deprecated
public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
/**
* This item's action view collapses to a normal menu item.
* When expanded, the action view temporarily takes over
* a larger segment of its container.
*/
public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
/**
* Interface for the full API.
*/
interface MenuVersionImpl {
public boolean setShowAsAction(MenuItem item, int actionEnum);
public MenuItem setActionView(MenuItem item, View view);
}
/**
* Interface implementation that doesn't use anything about v4 APIs.
*/
static class BaseMenuVersionImpl implements MenuVersionImpl {
@Override
public boolean setShowAsAction(MenuItem item, int actionEnum) {
return false;
}
@Override
public MenuItem setActionView(MenuItem item, View view) {
return item;
}
}
/**
* Interface implementation for devices with at least v11 APIs.
*/
static class HoneycombMenuVersionImpl implements MenuVersionImpl {
@Override
public boolean setShowAsAction(MenuItem item, int actionEnum) {
MenuItemCompatHoneycomb.setShowAsAction(item, actionEnum);
return true;
}
@Override
public MenuItem setActionView(MenuItem item, View view) {
return MenuItemCompatHoneycomb.setActionView(item, view);
}
}
/**
* Select the correct implementation to use for the current platform.
*/
static final MenuVersionImpl IMPL;
static {
if (android.os.Build.VERSION.SDK_INT >= 11) {
IMPL = new HoneycombMenuVersionImpl();
} else {
IMPL = new BaseMenuVersionImpl();
}
}
// -------------------------------------------------------------------
/** @Deprecated Use {@link MenuItem#setShowAsAction(int)}. */
@Deprecated
public static boolean setShowAsAction(MenuItem item, int actionEnum) {
return IMPL.setShowAsAction(item, actionEnum);
}
/** @Deprecated Use {@link MenuItem#setActionView(int)}. */
@Deprecated
public static MenuItem setActionView(MenuItem item, View view) {
return IMPL.setActionView(item, view);
}
}

@ -0,0 +1,33 @@
/*
* 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 android.support.v4.view;
import android.view.MenuItem;
import android.view.View;
/**
* Implementation of menu compatibility that can call Honeycomb APIs.
*/
class MenuItemCompatHoneycomb {
public static void setShowAsAction(MenuItem item, int actionEnum) {
item.setShowAsAction(actionEnum);
}
public static MenuItem setActionView(MenuItem item, View view) {
return item.setActionView(view);
}
}

@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.MotionEvent;
/**
* Helper for accessing newer features in MotionEvent.
* Helper for accessing features in {@link MotionEvent} introduced
* after API level 4 in a backwards compatible fashion.
*/
public class MotionEventCompat {
/**

@ -16,8 +16,11 @@
package android.support.v4.view;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
/**
* Base class providing the adapter to populate pages inside of
@ -25,56 +28,169 @@ import android.view.View;
* specific implementation of this, such as
* {@link android.support.v4.app.FragmentPagerAdapter} or
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*
* <p>When you implement a PagerAdapter, you must override the following methods
* at minimum:</p>
* <ul>
* <li>{@link #instantiateItem(ViewGroup, int)}</li>
* <li>{@link #destroyItem(ViewGroup, int, Object)}</li>
* <li>{@link #getCount()}</li>
* <li>{@link #isViewFromObject(View, Object)}</li>
* </ul>
*
* <p>PagerAdapter is more general than the adapters used for
* {@link android.widget.AdapterView AdapterViews}. Instead of providing a
* View recycling mechanism directly ViewPager uses callbacks to indicate the
* steps taken during an update. A PagerAdapter may implement a form of View
* recycling if desired or use a more sophisticated method of managing page
* Views such as Fragment transactions where each page is represented by its
* own Fragment.</p>
*
* <p>ViewPager associates each page with a key Object instead of working with
* Views directly. This key is used to track and uniquely identify a given page
* independent of its position in the adapter. A call to the PagerAdapter method
* {@link #startUpdate(ViewGroup)} indicates that the contents of the ViewPager
* are about to change. One or more calls to {@link #instantiateItem(ViewGroup, int)}
* and/or {@link #destroyItem(ViewGroup, int, Object)} will follow, and the end
* of an update will be signaled by a call to {@link #finishUpdate(ViewGroup)}.
* By the time {@link #finishUpdate(ViewGroup) finishUpdate} returns the views
* associated with the key objects returned by
* {@link #instantiateItem(ViewGroup, int) instantiateItem} should be added to
* the parent ViewGroup passed to these methods and the views associated with
* the keys passed to {@link #destroyItem(ViewGroup, int, Object) destroyItem}
* should be removed. The method {@link #isViewFromObject(View, Object)} identifies
* whether a page View is associated with a given key object.</p>
*
* <p>A very simple PagerAdapter may choose to use the page Views themselves
* as key objects, returning them from {@link #instantiateItem(ViewGroup, int)}
* after creation and adding them to the parent ViewGroup. A matching
* {@link #destroyItem(ViewGroup, int, Object)} implementation would remove the
* View from the parent ViewGroup and {@link #isViewFromObject(View, Object)}
* could be implemented as <code>return view == object;</code>.</p>
*
* <p>PagerAdapter supports data set changes. Data set changes must occur on the
* main thread and must end with a call to {@link #notifyDataSetChanged()} similar
* to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data
* set change may involve pages being added, removed, or changing position. The
* ViewPager will keep the current page active provided the adapter implements
* the method {@link #getItemPosition(Object)}.</p>
*/
public abstract class PagerAdapter {
private DataSetObserver mObserver;
private DataSetObservable mObservable = new DataSetObservable();
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
/**
* Used to watch for changes within the adapter.
* Return the number of views available.
*/
interface DataSetObserver {
public void onDataSetChanged();
public abstract int getCount();
/**
* Called when a change in the shown pages is going to start being made.
* @param container The containing View which is displaying this adapter's
* page views.
*/
public void startUpdate(ViewGroup container) {
startUpdate((View) container);
}
/**
* Return the number of views available.
* Create the page for the given position. The adapter is responsible
* for adding the view to the container given here, although it only
* must ensure this is done by the time it returns from
* {@link #finishUpdate(ViewGroup)}.
*
* @param container The containing View in which the page will be shown.
* @param position The page position to be instantiated.
* @return Returns an Object representing the new page. This does not
* need to be a View, but can be some other container of the page.
*/
public abstract int getCount();
public Object instantiateItem(ViewGroup container, int position) {
return instantiateItem((View) container, position);
}
/**
* Remove a page for the given position. The adapter is responsible
* for removing the view from its container, although it only must ensure
* this is done by the time it returns from {@link #finishUpdate(ViewGroup)}.
*
* @param container The containing View from which the page will be removed.
* @param position The page position to be removed.
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
*/
public void destroyItem(ViewGroup container, int position, Object object) {
destroyItem((View) container, position, object);
}
/**
* Called to inform the adapter of which item is currently considered to
* be the "primary", that is the one show to the user as the current page.
*
* @param container The containing View from which the page will be removed.
* @param position The page position that is now the primary.
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
*/
public void setPrimaryItem(ViewGroup container, int position, Object object) {
setPrimaryItem((View) container, position, object);
}
/**
* Called when the a change in the shown pages has been completed. At this
* point you must ensure that all of the pages have actually been added or
* removed from the container as appropriate.
* @param container The containing View which is displaying this adapter's
* page views.
*/
public void finishUpdate(ViewGroup container) {
finishUpdate((View) container);
}
/**
* Called when a change in the shown pages is going to start being made.
* @param container The containing View which is displaying this adapter's
* page views.
*
* @deprecated Use {@link #startUpdate(ViewGroup)}
*/
public abstract void startUpdate(View container);
public void startUpdate(View container) {
}
/**
* Create the page for the given position. The adapter is responsible
* for adding the view to the container given here, although it only
* must ensure this is done by the time it returns from
* {@link #finishUpdate()}.
* {@link #finishUpdate(ViewGroup)}.
*
* @param container The containing View in which the page will be shown.
* @param position The page position to be instantiated.
* @return Returns an Object representing the new page. This does not
* need to be a View, but can be some other container of the page.
*
* @deprecated Use {@link #instantiateItem(ViewGroup, int)}
*/
public abstract Object instantiateItem(View container, int position);
public Object instantiateItem(View container, int position) {
throw new UnsupportedOperationException(
"Required method instantiateItem was not overridden");
}
/**
* Remove a page for the given position. The adapter is responsible
* for removing the view from its container, although it only must ensure
* this is done by the time it returns from {@link #finishUpdate()}.
* this is done by the time it returns from {@link #finishUpdate(View)}.
*
* @param container The containing View from which the page will be removed.
* @param position The page position to be removed.
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
*
* @deprecated Use {@link #destroyItem(ViewGroup, int, Object)}
*/
public abstract void destroyItem(View container, int position, Object object);
public void destroyItem(View container, int position, Object object) {
throw new UnsupportedOperationException("Required method destroyItem was not overridden");
}
/**
* Called to inform the adapter of which item is currently considered to
@ -84,6 +200,8 @@ public abstract class PagerAdapter {
* @param position The page position that is now the primary.
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
*
* @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)}
*/
public void setPrimaryItem(View container, int position, Object object) {
}
@ -94,14 +212,42 @@ public abstract class PagerAdapter {
* removed from the container as appropriate.
* @param container The containing View which is displaying this adapter's
* page views.
*
* @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)}
*/
public abstract void finishUpdate(View container);
public void finishUpdate(View container) {
}
/**
* Determines whether a page View is associated with a specific key object
* as returned by {@link #instantiateItem(ViewGroup, int)}. This method is
* required for a PagerAdapter to function properly.
*
* @param view Page View to check for association with <code>object</code>
* @param object Object to check for association with <code>view</code>
* @return true if <code>view</code> is associated with the key object <code>object</code>
*/
public abstract boolean isViewFromObject(View view, Object object);
public abstract Parcelable saveState();
/**
* Save any instance state associated with this adapter and its pages that should be
* restored if the current UI state needs to be reconstructed.
*
* @return Saved state for this adapter
*/
public Parcelable saveState() {
return null;
}
public abstract void restoreState(Parcelable state, ClassLoader loader);
/**
* Restore any instance state associated with this adapter and its pages
* that was previously saved by {@link #saveState()}.
*
* @param state State previously saved by a call to {@link #saveState()}
* @param loader A ClassLoader that should be used to instantiate any restored objects
*/
public void restoreState(Parcelable state, ClassLoader loader) {
}
/**
* Called when the host view is attempting to determine if an item's position
@ -127,12 +273,27 @@ public abstract class PagerAdapter {
* and associated views should update.
*/
public void notifyDataSetChanged() {
if (mObserver != null) {
mObserver.onDataSetChanged();
}
mObservable.notifyChanged();
}
void registerDataSetObserver(DataSetObserver observer) {
mObservable.registerObserver(observer);
}
void setDataSetObserver(DataSetObserver observer) {
mObserver = observer;
void unregisterDataSetObserver(DataSetObserver observer) {
mObservable.unregisterObserver(observer);
}
/**
* This method may be called by the ViewPager to obtain a title string
* to describe the specified page. This method may return null
* indicating no title for this page. The default implementation returns
* null.
*
* @param position The position of the title requested
* @return A title for the requested page
*/
public CharSequence getPageTitle(int position) {
return null;
}
}

@ -0,0 +1,326 @@
/*
* 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 android.support.v4.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.TextView;
/**
* PagerTitleStrip is a non-interactive indicator of the current, next,
* and previous pages of a {@link ViewPager}. It is intended to be used as a
* child view of a ViewPager widget in your XML layout.
* Add it as a child of a ViewPager in your layout file and set its
* android:layout_gravity to TOP or BOTTOM to pin it to the top or bottom
* of the ViewPager. The title from each page is supplied by the method
* {@link PagerAdapter#getPageTitle(int)} in the adapter supplied to
* the ViewPager.
*/
public class PagerTitleStrip extends ViewGroup implements ViewPager.Decor {
//private static final String TAG = "PagerTitleStrip";
ViewPager mPager;
private TextView mPrevText;
private TextView mCurrText;
private TextView mNextText;
private int mLastKnownCurrentPage = -1;
private float mLastKnownPositionOffset = -1;
private int mScaledTextSpacing;
private boolean mUpdatingText;
private boolean mUpdatingPositions;
private final PageListener mPageListener = new PageListener();
private static final int[] ATTRS = new int[] {
android.R.attr.textAppearance,
android.R.attr.textColor,
android.R.attr.textSize
};
private static final int SIDE_ALPHA = 0x99; // single-byte alpha, 0 = invisible, FF = opaque
private static final int TEXT_SPACING = 16; // dip
public PagerTitleStrip(Context context) {
this(context, null);
}
public PagerTitleStrip(Context context, AttributeSet attrs) {
super(context, attrs);
addView(mPrevText = new TextView(context));
addView(mCurrText = new TextView(context));
addView(mNextText = new TextView(context));
final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
final int textAppearance = a.getResourceId(0, 0);
if (textAppearance != 0) {
mPrevText.setTextAppearance(context, textAppearance);
mCurrText.setTextAppearance(context, textAppearance);
mNextText.setTextAppearance(context, textAppearance);
}
if (a.hasValue(1)) {
final int textColor = a.getColor(1, 0);
mPrevText.setTextColor(textColor);
mCurrText.setTextColor(textColor);
mNextText.setTextColor(textColor);
}
final int textSize = a.getDimensionPixelSize(2, 0);
if (textSize != 0) {
mPrevText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
mCurrText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
mNextText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
a.recycle();
final int defaultColor = mPrevText.getTextColors().getDefaultColor();
final int transparentColor = (SIDE_ALPHA << 24) | (defaultColor & 0xFFFFFF);
mPrevText.setTextColor(transparentColor);
mNextText.setTextColor(transparentColor);
mPrevText.setEllipsize(TruncateAt.END);
mCurrText.setEllipsize(TruncateAt.END);
mNextText.setEllipsize(TruncateAt.END);
mPrevText.setSingleLine();
mCurrText.setSingleLine();
mNextText.setSingleLine();
final float density = context.getResources().getDisplayMetrics().density;
mScaledTextSpacing = (int) (TEXT_SPACING * density);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final ViewParent parent = getParent();
if (!(parent instanceof ViewPager)) {
throw new IllegalStateException(
"PagerTitleStrip must be a direct child of a ViewPager.");
}
final ViewPager pager = (ViewPager) parent;
final PagerAdapter adapter = pager.getAdapter();
pager.setInternalPageChangeListener(mPageListener);
pager.setOnAdapterChangeListener(mPageListener);
mPager = pager;
updateAdapter(null, adapter);
}
@Override
protected void onDetachedFromWindow() {
updateAdapter(mPager.getAdapter(), null);
mPager.setInternalPageChangeListener(null);
mPager.setOnAdapterChangeListener(null);
mPager = null;
}
void updateText(int currentItem, PagerAdapter adapter) {
final int itemCount = adapter != null ? adapter.getCount() : 0;
mUpdatingText = true;
CharSequence text = null;
if (currentItem >= 1 && adapter != null) {
text = adapter.getPageTitle(currentItem - 1);
}
mPrevText.setText(text);
mCurrText.setText(adapter != null ? adapter.getPageTitle(currentItem) : null);
text = null;
if (currentItem + 1 < itemCount && adapter != null) {
text = adapter.getPageTitle(currentItem + 1);
}
mNextText.setText(text);
// Measure everything
final int width = getWidth() - getPaddingLeft() - getPaddingRight();
final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom();
final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (width * 0.8f),
MeasureSpec.AT_MOST);
final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
mPrevText.measure(childWidthSpec, childHeightSpec);
mCurrText.measure(childWidthSpec, childHeightSpec);
mNextText.measure(childWidthSpec, childHeightSpec);
mLastKnownCurrentPage = currentItem;
if (!mUpdatingPositions) {
updateTextPositions(currentItem, mLastKnownPositionOffset);
}
mUpdatingText = false;
}
@Override
public void requestLayout() {
if (!mUpdatingText) {
super.requestLayout();
}
}
void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
if (oldAdapter != null) {
oldAdapter.unregisterDataSetObserver(mPageListener);
}
if (newAdapter != null) {
newAdapter.registerDataSetObserver(mPageListener);
}
if (mPager != null) {
mLastKnownCurrentPage = -1;
mLastKnownPositionOffset = -1;
updateText(mPager.getCurrentItem(), newAdapter);
requestLayout();
}
}
void updateTextPositions(int position, float positionOffset) {
if (position != mLastKnownCurrentPage) {
updateText(position, mPager.getAdapter());
} else if (positionOffset == mLastKnownPositionOffset) {
return;
}
mUpdatingPositions = true;
final int prevWidth = mPrevText.getMeasuredWidth();
final int currWidth = mCurrText.getMeasuredWidth();
final int nextWidth = mNextText.getMeasuredWidth();
final int halfCurrWidth = currWidth / 2;
final int stripWidth = getWidth();
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int textPaddedLeft = paddingLeft + halfCurrWidth;
final int textPaddedRight = paddingRight + halfCurrWidth;
final int contentWidth = stripWidth - textPaddedLeft - textPaddedRight;
float currOffset = positionOffset + 0.5f;
if (currOffset > 1.f) {
currOffset -= 1.f;
}
final int currCenter = stripWidth - textPaddedRight - (int) (contentWidth * currOffset);
final int currLeft = currCenter - currWidth / 2;
final int currRight = currLeft + currWidth;
mCurrText.layout(currLeft, paddingTop, currRight,
paddingTop + mCurrText.getMeasuredHeight());
final int prevLeft = Math.min(paddingLeft, currLeft - mScaledTextSpacing - prevWidth);
mPrevText.layout(prevLeft, paddingTop, prevLeft + prevWidth,
paddingTop + mPrevText.getMeasuredHeight());
final int nextLeft = Math.max(stripWidth - paddingRight - nextWidth,
currRight + mScaledTextSpacing);
mNextText.layout(nextLeft, paddingTop, nextLeft + nextWidth,
paddingTop + mNextText.getMeasuredHeight());
mLastKnownPositionOffset = positionOffset;
mUpdatingPositions = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Must measure with an exact width");
}
int childHeight = heightSize;
int minHeight = 0;
int padding = 0;
final Drawable bg = getBackground();
if (bg != null) {
minHeight = bg.getIntrinsicHeight();
}
padding = getPaddingTop() + getPaddingBottom();
childHeight -= padding;
final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * 0.8f),
MeasureSpec.AT_MOST);
final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode);
mPrevText.measure(childWidthSpec, childHeightSpec);
mCurrText.measure(childWidthSpec, childHeightSpec);
mNextText.measure(childWidthSpec, childHeightSpec);
if (heightMode == MeasureSpec.EXACTLY) {
setMeasuredDimension(widthSize, heightSize);
} else {
int textHeight = mCurrText.getMeasuredHeight();
setMeasuredDimension(widthSize, Math.max(minHeight, textHeight + padding));
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mPager != null) {
updateTextPositions(mPager.getCurrentItem(), 0.f);
}
}
private class PageListener extends DataSetObserver implements ViewPager.OnPageChangeListener,
ViewPager.OnAdapterChangeListener {
private int mScrollState;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset > 0.5f) {
// Consider ourselves to be on the next page when we're 50% of the way there.
position++;
}
updateTextPositions(position, positionOffset);
}
@Override
public void onPageSelected(int position) {
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
// Only update the text here if we're not dragging or settling.
updateText(mPager.getCurrentItem(), mPager.getAdapter());
}
}
@Override
public void onPageScrollStateChanged(int state) {
mScrollState = state;
}
@Override
public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
updateAdapter(oldAdapter, newAdapter);
}
@Override
public void onChanged() {
updateText(mPager.getCurrentItem(), mPager.getAdapter());
}
}
}

@ -3,7 +3,37 @@ package android.support.v4.view;
import android.graphics.drawable.Drawable;
import android.view.View;
public interface SubMenu extends android.view.SubMenu {
public interface SubMenu extends android.view.SubMenu, Menu {
@Override
MenuItem add(CharSequence title);
@Override
MenuItem add(int groupId, int itemId, int order, int titleRes);
@Override
MenuItem add(int titleRes);
@Override
MenuItem add(int groupId, int itemId, int order, CharSequence title);
@Override
SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title);
@Override
SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes);
@Override
SubMenu addSubMenu(CharSequence title);
@Override
SubMenu addSubMenu(int titleRes);
@Override
MenuItem findItem(int id);
@Override
MenuItem getItem(int index);
@Override
MenuItem getItem();

@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.VelocityTracker;
/**
* Helper for accessing newer features in VelocityTracker.
* Helper for accessing features in {@link VelocityTracker}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class VelocityTrackerCompat {
/**

@ -21,7 +21,8 @@ import android.view.View;
import android.view.accessibility.AccessibilityEvent;
/**
* Helper for accessing newer features in View.
* Helper for accessing features in {@link View} introduced after API
* level 4 in a backwards compatible fashion.
*/
public class ViewCompat {
/**
@ -129,34 +130,174 @@ public class ViewCompat {
}
}
/**
* Check if this view can be scrolled horizontally in a certain direction.
*
* @param v The View against which to invoke the method.
* @param direction Negative to check scrolling left, positive to check scrolling right.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*/
public static boolean canScrollHorizontally(View v, int direction) {
return IMPL.canScrollHorizontally(v, direction);
}
/**
* Check if this view can be scrolled vertically in a certain direction.
*
* @param v The View against which to invoke the method.
* @param direction Negative to check scrolling up, positive to check scrolling down.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*/
public static boolean canScrollVertically(View v, int direction) {
return IMPL.canScrollVertically(v, direction);
}
/**
* Returns the over-scroll mode for this view. The result will be
* one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
* @param v The View against which to invoke the method.
* @return This view's over-scroll mode.
*/
public static int getOverScrollMode(View v) {
return IMPL.getOverScrollMode(v);
}
public static void setOverScrollMode(View v, int mode) {
IMPL.setOverScrollMode(v, mode);
/**
* Set the over-scroll mode for this view. Valid over-scroll modes are
* {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
* Setting the over-scroll mode of a view will have an effect only if the
* view is capable of scrolling.
*
* @param v The View against which to invoke the method.
* @param overScrollMode The new over-scroll mode for this view.
*/
public static void setOverScrollMode(View v, int overScrollMode) {
IMPL.setOverScrollMode(v, overScrollMode);
}
/**
* Called from {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
* giving a chance to this View to populate the accessibility event with its
* text content. While this method is free to modify event
* attributes other than text content, doing so should normally be performed in
* {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)}.
* <p>
* Example: Adding formatted date string to an accessibility event in addition
* to the text added by the super implementation:
* <pre> public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
* super.onPopulateAccessibilityEvent(event);
* final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
* String selectedDateUtterance = DateUtils.formatDateTime(mContext,
* mCurrentDate.getTimeInMillis(), flags);
* event.getText().add(selectedDateUtterance);
* }</pre>
* <p>
* If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
* {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
* {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View,
* AccessibilityEvent)}
* is responsible for handling this call.
* </p>
* <p class="note"><strong>Note:</strong> Always call the super implementation before adding
* information to the event, in case the default implementation has basic information to add.
* </p>
*
* @param v The View against which to invoke the method.
* @param event The accessibility event which to populate.
*
* @see View#sendAccessibilityEvent(int)
* @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*/
public static void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) {
IMPL.onPopulateAccessibilityEvent(v, event);
}
/**
* Initializes an {@link AccessibilityEvent} with information about
* this View which is the event source. In other words, the source of
* an accessibility event is the view whose state change triggered firing
* the event.
* <p>
* Example: Setting the password property of an event in addition
* to properties set by the super implementation:
* <pre> public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
* super.onInitializeAccessibilityEvent(event);
* event.setPassword(true);
* }</pre>
* <p>
* If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
* {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
* {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View,
* AccessibilityEvent)}
* is responsible for handling this call.
* </p>
* <p class="note"><strong>Note:</strong> Always call the super implementation before adding
* information to the event, in case the default implementation has basic information to add.
* </p>
*
* @param v The View against which to invoke the method.
* @param event The event to initialize.
*
* @see View#sendAccessibilityEvent(int)
* @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*/
public static void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) {
IMPL.onInitializeAccessibilityEvent(v, event);
}
/**
* Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information
* about this view. The base implementation sets:
* <ul>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},</li>
* <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},</li>
* </ul>
* <p>
* Subclasses should override this method, call the super implementation,
* and set additional attributes.
* </p>
* <p>
* If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
* {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
* {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View,
* android.view.accessibility.AccessibilityNodeInfo)}
* is responsible for handling this call.
* </p>
*
* @param v The View against which to invoke the method.
* @param info The instance to initialize.
*/
public static void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) {
IMPL.onInitializeAccessibilityNodeInfo(v, info);
}
/**
* Sets a delegate for implementing accessibility support via compositon as
* opposed to inheritance. The delegate's primary use is for implementing
* backwards compatible widgets. For more details see
* {@link android.view.View.AccessibilityDelegate}.
*
* @param v The View against which to invoke the method.
* @param delegate The delegate instance.
*
* @see android.view.View.AccessibilityDelegate
*/
public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
IMPL.setAccessibilityDelegate(v, delegate);
}

@ -18,7 +18,7 @@ package android.support.v4.view;
import android.view.View;
public class ViewCompatGingerbread {
class ViewCompatGingerbread {
public static int getOverScrollMode(View v) {
return v.getOverScrollMode();
}

@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.ViewConfiguration;
/**
* Helper for accessing newer features in ViewConfiguration.
* Helper for accessing features in {@link ViewConfiguration}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class ViewConfigurationCompat {
/**

@ -22,7 +22,8 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
* Helper for accessing newer features in ViewGroup.
* Helper for accessing features in {@link ViewGroup}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class ViewGroupCompat {

@ -17,9 +17,12 @@
package android.support.v4.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@ -29,6 +32,7 @@ import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
@ -54,6 +58,24 @@ import java.util.Comparator;
* development. The API will likely change in later updates of
* the compatibility library, requiring changes to the source code
* of apps when they are compiled against the newer version.</p>
*
* <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
* which is a convenient way to supply and manage the lifecycle of each page.
* There are standard adapters implemented for using fragments with the ViewPager,
* which cover the most common use cases. These are
* {@link android.support.v4.app.FragmentPagerAdapter},
* {@link android.support.v4.app.FragmentStatePagerAdapter},
* {@link android.support.v13.app.FragmentPagerAdapter}, and
* {@link android.support.v13.app.FragmentStatePagerAdapter}; each of these
* classes have simple code showing how to build a full user interface
* with them.
*
* <p>Here is a more complicated example of ViewPager, using it in conjuction
* with {@link android.app.ActionBar} tabs. You can find other examples of using
* ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
*
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
* complete}
*/
public class ViewPager extends ViewGroup {
private static final String TAG = "ViewPager";
@ -63,6 +85,11 @@ public class ViewPager extends ViewGroup {
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private static final int MAX_SETTLE_DURATION = 600; // ms
private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
private static final int[] LAYOUT_ATTRS = new int[] {
android.R.attr.layout_gravity
};
static class ItemInfo {
Object object;
@ -78,10 +105,8 @@ public class ViewPager extends ViewGroup {
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
// _o(t) = t * t * ((tension + 1) * t + tension)
// o(t) = _o(t - 1) + 1
t -= 1.0f;
return t * t * t + 1.0f;
return t * t * t * t * t + 1.0f;
}
};
@ -93,10 +118,12 @@ public class ViewPager extends ViewGroup {
private Parcelable mRestoredAdapterState = null;
private ClassLoader mRestoredClassLoader = null;
private Scroller mScroller;
private PagerAdapter.DataSetObserver mObserver;
private PagerObserver mObserver;
private int mPageMargin;
private Drawable mMarginDrawable;
private int mTopPageBounds;
private int mBottomPageBounds;
private int mChildWidthMeasureSpec;
private int mChildHeightMeasureSpec;
@ -134,8 +161,7 @@ public class ViewPager extends ViewGroup {
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
private float mBaseLineFlingVelocity;
private float mFlingVelocityInfluence;
private int mFlingDistance;
private boolean mFakeDragging;
private long mFakeDragBeginTime;
@ -144,8 +170,12 @@ public class ViewPager extends ViewGroup {
private EdgeEffectCompat mRightEdge;
private boolean mFirstLayout = true;
private boolean mCalledSuper;
private int mDecorChildCount;
private OnPageChangeListener mOnPageChangeListener;
private OnPageChangeListener mInternalPageChangeListener;
private OnAdapterChangeListener mAdapterChangeListener;
/**
* Indicates that the pager is in an idle, settled state. The current page
@ -224,6 +254,19 @@ public class ViewPager extends ViewGroup {
}
}
/**
* Used internally to monitor when adapters are switched.
*/
interface OnAdapterChangeListener {
public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
}
/**
* Used internally to tag special types of child views that should be added as
* pager decorations by default.
*/
interface Decor {}
public ViewPager(Context context) {
super(context);
initViewPager();
@ -247,9 +290,8 @@ public class ViewPager extends ViewGroup {
mLeftEdge = new EdgeEffectCompat(context);
mRightEdge = new EdgeEffectCompat(context);
float density = context.getResources().getDisplayMetrics().density;
mBaseLineFlingVelocity = 2500.0f * density;
mFlingVelocityInfluence = 0.4f;
final float density = context.getResources().getDisplayMetrics().density;
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
}
private void setScrollState(int newState) {
@ -263,9 +305,14 @@ public class ViewPager extends ViewGroup {
}
}
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public void setAdapter(PagerAdapter adapter) {
if (mAdapter != null) {
mAdapter.setDataSetObserver(null);
mAdapter.unregisterDataSetObserver(mObserver);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
@ -273,18 +320,19 @@ public class ViewPager extends ViewGroup {
}
mAdapter.finishUpdate(this);
mItems.clear();
removeAllViews();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new DataSetObserver();
mObserver = new PagerObserver();
}
mAdapter.setDataSetObserver(mObserver);
mAdapter.registerDataSetObserver(mObserver);
mPopulatePending = false;
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
@ -296,12 +344,36 @@ public class ViewPager extends ViewGroup {
populate();
}
}
if (mAdapterChangeListener != null && oldAdapter != adapter) {
mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
}
}
private void removeNonDecorViews() {
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
removeViewAt(i);
i--;
}
}
}
/**
* Retrieve the current adapter supplying pages.
*
* @return The currently registered PagerAdapter
*/
public PagerAdapter getAdapter() {
return mAdapter;
}
void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
mAdapterChangeListener = listener;
}
/**
* Set the currently selected page. If the ViewPager has already been through its first
* layout there will be a smooth animated transition between the current item and the
@ -365,19 +437,43 @@ public class ViewPager extends ViewGroup {
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
} else {
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
completeScroll();
scrollTo(destX, 0);
}
}
/**
* Set a listener that will be invoked whenever the page changes or is incrementally
* scrolled. See {@link OnPageChangeListener}.
*
* @param listener Listener to set
*/
public void setOnPageChangeListener(OnPageChangeListener listener) {
mOnPageChangeListener = listener;
}
/**
* Set a separate OnPageChangeListener for internal use by the support library.
*
* @param listener Listener to set
* @return The old listener that was set, if any.
*/
OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
OnPageChangeListener oldListener = mInternalPageChangeListener;
mInternalPageChangeListener = listener;
return oldListener;
}
/**
* Returns the number of pages that will be retained to either side of the
* current page in the view hierarchy in an idle state. Defaults to 1.
@ -527,14 +623,19 @@ public class ViewPager extends ViewGroup {
mScrolling = true;
setScrollState(SCROLL_STATE_SETTLING);
final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin);
int duration = (int) (pageDelta * 100);
final int width = getWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth *
distanceInfluenceForSnapDuration(distanceRatio);
int duration = 0;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
duration += 100;
final float pageDelta = (float) Math.abs(dx) / (width + mPageMargin);
duration = (int) ((pageDelta + 1) * 100);
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
@ -559,6 +660,7 @@ public class ViewPager extends ViewGroup {
boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
int newCurrItem = -1;
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
@ -570,6 +672,12 @@ public class ViewPager extends ViewGroup {
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
@ -591,6 +699,10 @@ public class ViewPager extends ViewGroup {
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (newCurrItem >= 0) {
@ -708,6 +820,12 @@ public class ViewPager extends ViewGroup {
}
}
/**
* This is the persistent state that is saved by ViewPager. Only needed
* if you are creating a sublass of ViewPager that must save its own
* state, in which case it should implement a subclass of this which
* contains that state.
*/
public static class SavedState extends BaseSavedState {
int position;
Parcelable adapterState;
@ -786,8 +904,16 @@ public class ViewPager extends ViewGroup {
}
@Override
public void addView(View child, int index, LayoutParams params) {
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
final LayoutParams lp = (LayoutParams) params;
lp.isDecor |= child instanceof Decor;
if (mInLayout) {
if (lp != null && lp.isDecor) {
throw new IllegalStateException("Cannot add pager decor view during layout");
}
addViewInLayout(child, index, params);
child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
} else {
@ -841,24 +967,67 @@ public class ViewPager extends ViewGroup {
getDefaultSize(0, heightMeasureSpec));
// Children are just made to fill our space.
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
/*
* Make sure all children have been properly measured. Decor views first.
* Right now we cheat and make this less complicated by assuming decor
* views won't intersect. We will pin to edges based on gravity.
*/
int size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp != null && lp.isDecor) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
Log.d(TAG, "gravity: " + lp.gravity + " hgrav: " + hgrav + " vgrav: " + vgrav);
int widthMode = MeasureSpec.AT_MOST;
int heightMode = MeasureSpec.AT_MOST;
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
if (consumeVertical) {
widthMode = MeasureSpec.EXACTLY;
} else if (consumeHorizontal) {
heightMode = MeasureSpec.EXACTLY;
}
final int widthSpec = MeasureSpec.makeMeasureSpec(childWidthSize, widthMode);
final int heightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, heightMode);
child.measure(widthSpec, heightSpec);
if (consumeVertical) {
childHeightSize -= child.getMeasuredHeight();
} else if (consumeHorizontal) {
childWidthSize -= child.getMeasuredWidth();
}
}
}
}
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate();
mInLayout = false;
// Make sure all children have been properly measured.
final int size = getChildCount();
// Page views next.
size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
+ ": " + mChildWidthMeasureSpec);
child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
+ ": " + mChildWidthMeasureSpec);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null || !lp.isDecor) {
child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
}
}
}
}
@ -903,23 +1072,81 @@ public class ViewPager extends ViewGroup {
mInLayout = false;
final int count = getChildCount();
final int width = r-l;
int width = r - l;
int height = b - t;
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
final int scrollX = getScrollX();
int decorCount = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
ItemInfo ii;
if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
int loff = (width + mPageMargin) * ii.position;
int childLeft = getPaddingLeft() + loff;
int childTop = getPaddingTop();
if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ "x" + child.getMeasuredHeight());
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
ItemInfo ii;
int childLeft = 0;
int childTop = 0;
if (lp.isDecor) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
paddingLeft += child.getMeasuredWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
paddingLeft);
break;
case Gravity.RIGHT:
childLeft = width - paddingRight - child.getMeasuredWidth();
paddingRight += child.getMeasuredWidth();
break;
}
switch (vgrav) {
default:
childTop = paddingTop;
break;
case Gravity.TOP:
childTop = paddingTop;
paddingTop += child.getMeasuredHeight();
break;
case Gravity.CENTER_VERTICAL:
childTop = Math.max((height - child.getMeasuredHeight()) / 2,
paddingTop);
break;
case Gravity.BOTTOM:
childTop = height - paddingBottom - child.getMeasuredHeight();
paddingBottom += child.getMeasuredHeight();
break;
}
childLeft += scrollX;
decorCount++;
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
} else if ((ii = infoForChild(child)) != null) {
int loff = (width + mPageMargin) * ii.position;
childLeft = paddingLeft + loff;
childTop = paddingTop;
if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ "x" + child.getMeasuredHeight());
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
mTopPageBounds = paddingTop;
mBottomPageBounds = height - paddingBottom;
mDecorChildCount = decorCount;
mFirstLayout = false;
}
@ -936,14 +1163,7 @@ public class ViewPager extends ViewGroup {
if (oldX != x || oldY != y) {
scrollTo(x, y);
}
if (mOnPageChangeListener != null) {
final int widthWithMargin = getWidth() + mPageMargin;
final int position = x / widthWithMargin;
final int offsetPixels = x % widthWithMargin;
final float offset = (float) offsetPixels / widthWithMargin;
mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
pageScrolled(x);
}
// Keep on drawing until the animation has finished.
@ -956,6 +1176,82 @@ public class ViewPager extends ViewGroup {
completeScroll();
}
private void pageScrolled(int xpos) {
final int widthWithMargin = getWidth() + mPageMargin;
final int position = xpos / widthWithMargin;
final int offsetPixels = xpos % widthWithMargin;
final float offset = (float) offsetPixels / widthWithMargin;
mCalledSuper = false;
onPageScrolled(position, offset, offsetPixels);
if (!mCalledSuper) {
throw new IllegalStateException(
"onPageScrolled did not call superclass implementation");
}
}
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
* If you override this method you must call through to the superclass implementation
* (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
* returns.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param offset Value from [0, 1) indicating the offset from the page at position.
* @param offsetPixels Value in pixels indicating the offset from position.
*/
protected void onPageScrolled(int position, float offset, int offsetPixels) {
// Offset any decor views if needed - keep them on-screen at all times.
if (mDecorChildCount > 0) {
final int scrollX = getScrollX();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
final int width = getWidth();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) continue;
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
int childLeft = 0;
switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
paddingLeft += child.getWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
paddingLeft);
break;
case Gravity.RIGHT:
childLeft = width - paddingRight - child.getMeasuredWidth();
paddingRight += child.getMeasuredWidth();
break;
}
childLeft += scrollX;
final int childOffset = childLeft - child.getLeft();
if (childOffset != 0) {
child.offsetLeftAndRight(childOffset);
}
}
}
if (mOnPageChangeListener != null) {
mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
}
if (mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
}
mCalledSuper = true;
}
private void completeScroll() {
boolean needPopulate = mScrolling;
if (needPopulate) {
@ -1002,6 +1298,10 @@ public class ViewPager extends ViewGroup {
mIsBeingDragged = false;
mIsUnableToDrag = false;
mActivePointerId = INVALID_POINTER;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
return false;
}
@ -1041,9 +1341,6 @@ public class ViewPager extends ViewGroup {
final float xDiff = Math.abs(dx);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
//UNUSED: final int scrollX = getScrollX();
//UNUSED: final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
// scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
@ -1102,10 +1399,19 @@ public class ViewPager extends ViewGroup {
break;
}
if (!mIsBeingDragged) {
// Track the velocity as long as we aren't dragging.
// Once we start a real drag we will track in onTouchEvent.
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
@ -1198,13 +1504,7 @@ public class ViewPager extends ViewGroup {
// Don't lose the rounded component
mLastMotionX += scrollX - (int) scrollX;
scrollTo((int) scrollX, getScrollY());
if (mOnPageChangeListener != null) {
final int position = (int) scrollX / widthWithMargin;
final int positionOffsetPixels = (int) scrollX % widthWithMargin;
final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
mOnPageChangeListener.onPageScrolled(position, positionOffset,
positionOffsetPixels);
}
pageScrolled((int) scrollX);
}
break;
case MotionEvent.ACTION_UP:
@ -1217,7 +1517,13 @@ public class ViewPager extends ViewGroup {
final int widthWithMargin = getWidth() + mPageMargin;
final int scrollX = getScrollX();
final int currentPage = scrollX / widthWithMargin;
int nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin;
final int activePointerIndex =
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
final int totalDelta = (int) (x - mInitialMotionX);
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
mActivePointerId = INVALID_POINTER;
@ -1252,6 +1558,17 @@ public class ViewPager extends ViewGroup {
return true;
}
private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
int targetPage;
if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
targetPage = velocity > 0 ? currentPage : currentPage + 1;
} else {
targetPage = (int) (currentPage + pageOffset + 0.5f);
}
return targetPage;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@ -1307,7 +1624,8 @@ public class ViewPager extends ViewGroup {
if (offset != 0) {
// Pages fit completely when settled; we only need to draw when in between
final int left = scrollX - offset + width;
mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight());
mMarginDrawable.setBounds(left, mTopPageBounds, left + mPageMargin,
mBottomPageBounds);
mMarginDrawable.draw(canvas);
}
}
@ -1366,16 +1684,13 @@ public class ViewPager extends ViewGroup {
int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
velocityTracker, mActivePointerId);
mPopulatePending = true;
if ((Math.abs(initialVelocity) > mMinimumVelocity)
|| Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
if (mLastMotionX > mInitialMotionX) {
setCurrentItemInternal(mCurItem-1, true, true);
} else {
setCurrentItemInternal(mCurItem+1, true, true);
}
} else {
setCurrentItemInternal(mCurItem, true, true);
}
final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
final int scrollX = getScrollX();
final int widthWithMargin = getWidth() + mPageMargin;
final int currentPage = scrollX / widthWithMargin;
final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin;
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
endDrag();
mFakeDragging = false;
@ -1409,13 +1724,7 @@ public class ViewPager extends ViewGroup {
// Don't lose the rounded component
mLastMotionX += scrollX - (int) scrollX;
scrollTo((int) scrollX, getScrollY());
if (mOnPageChangeListener != null) {
final int position = (int) scrollX / widthWithMargin;
final int positionOffsetPixels = (int) scrollX % widthWithMargin;
final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
mOnPageChangeListener.onPageScrolled(position, positionOffset,
positionOffsetPixels);
}
pageScrolled((int) scrollX);
// Synthesize an event for the VelocityTracker.
final long time = SystemClock.uptimeMillis();
@ -1537,10 +1846,14 @@ public class ViewPager extends ViewGroup {
handled = arrowScroll(FOCUS_RIGHT);
break;
case KeyEvent.KEYCODE_TAB:
if (KeyEventCompat.hasNoModifiers(event)) {
handled = arrowScroll(FOCUS_FORWARD);
} else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
handled = arrowScroll(FOCUS_BACKWARD);
if (Build.VERSION.SDK_INT >= 11) {
// The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
// before Android 3.0. Ignore the tab key on those devices.
if (KeyEventCompat.hasNoModifiers(event)) {
handled = arrowScroll(FOCUS_FORWARD);
} else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
handled = arrowScroll(FOCUS_BACKWARD);
}
}
break;
}
@ -1721,10 +2034,64 @@ public class ViewPager extends ViewGroup {
return false;
}
private class DataSetObserver implements PagerAdapter.DataSetObserver {
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return generateDefaultLayoutParams();
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams && super.checkLayoutParams(p);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
private class PagerObserver extends DataSetObserver {
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onDataSetChanged() {
public void onInvalidated() {
dataSetChanged();
}
}
/**
* Layout parameters that should be supplied for views added to a
* ViewPager.
*/
public static class LayoutParams extends ViewGroup.LayoutParams {
/**
* true if this view is a decoration on the pager itself and not
* a view supplied by the adapter.
*/
public boolean isDecor;
/**
* Where to position the view page within the overall ViewPager
* container; constants are defined in {@link android.view.Gravity}.
*/
public int gravity;
public LayoutParams() {
super(FILL_PARENT, FILL_PARENT);
}
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
gravity = a.getInteger(0, Gravity.NO_GRAVITY);
a.recycle();
}
}
}

@ -44,13 +44,6 @@ public abstract class Window extends android.view.Window {
*/
public static final long FEATURE_ACTION_BAR = android.view.Window.FEATURE_ACTION_BAR;
/**
* Enable the pre-3.0 action bar implementation to honor the 'withText'
* attribute on menu items regardless of display resolution and/or density.
*/
//Hopefully the native Window.FEATURE_XXX numbering won't get this high anytime soon.
public static final long FEATURE_ACTION_BAR_ITEM_TEXT = 31;
/**
* Flag for requesting an Action Bar that overlays window content. Normally
* an Action Bar will sit in the space above window content, but if this

@ -20,7 +20,8 @@ package android.support.v4.view.accessibility;
import android.view.accessibility.AccessibilityEvent;
/**
* Helper for accessing newer features in AccessibilityEvent.
* Helper for accessing features in {@link AccessibilityEvent}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityEventCompat {

@ -18,13 +18,15 @@ package android.support.v4.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
//import android.os.Build;
import android.view.accessibility.AccessibilityManager;
//import android.support.v4.view.accessibility.AccessibilityManagerCompatIcs.AccessibilityStateChangeListenerBridge;
import android.view.accessibility.AccessibilityManager;
import java.util.Collections;
import java.util.List;
/**
* Helper for accessing newer features in AccessibilityManager.
* Helper for accessing features in {@link AccessibilityManager}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityManagerCompat {
@ -60,12 +62,12 @@ public class AccessibilityManagerCompat {
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
AccessibilityManager manager, int feedbackTypeFlags) {
return null;
return Collections.emptyList();
}
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
AccessibilityManager manager) {
return null;
return Collections.emptyList();
}
public boolean isTouchExplorationEnabled(AccessibilityManager manager) {

@ -21,10 +21,12 @@ import android.graphics.Rect;
import android.view.View;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Helper for accessing AccessibilityNodeInfo from newer platform versions.
* Helper for accessing {@link android.view.accessibility.AccessibilityNodeInfo}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityNodeInfoCompat {
@ -100,7 +102,7 @@ public class AccessibilityNodeInfoCompat {
}
public List<Object> findAccessibilityNodeInfosByText(Object info, String text) {
return null;
return Collections.emptyList();
}
public int getActions(Object info) {

@ -20,10 +20,12 @@ package android.support.v4.view.accessibility;
import android.os.Parcelable;
import android.view.View;
import java.util.Collections;
import java.util.List;
/**
* Helper for accessing AccessibilityRecord from newer platform versions.
* Helper for accessing {@link android.view.accessibility.AccessibilityRecord}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityRecordCompat {
@ -141,7 +143,7 @@ public class AccessibilityRecordCompat {
}
public List<CharSequence> getText(Object record) {
return null;
return Collections.emptyList();
}
public int getToIndex(Object record) {

@ -20,10 +20,10 @@ import android.database.Cursor;
import android.widget.Filter;
/**
* <p>The CursorFilter delegates most of the work to the CursorAdapter.
* Subclasses should override these delegate methods to run the queries
* and convert the results into String that can be used by auto-completion
* widgets.</p>
* The CursorFilter delegates most of the work to the
* {@link android.widget.CursorAdapter}. Subclasses should override these
* delegate methods to run the queries and convert the results into String
* that can be used by auto-completion widgets.
*/
class CursorFilter extends Filter {

@ -19,7 +19,8 @@ import android.content.Context;
import android.graphics.Canvas;
/**
* Helper for accessing EdgeEffects from newer platform versions.
* Helper for accessing {@link android.widget.EdgeEffect} introduced after
* API level 4 in a backwards compatible fashion.
*
* This class is used to access {@link android.widget.EdgeEffect} on platform versions
* that support it. When running on older platforms it will result in no-ops. It should

@ -0,0 +1,155 @@
/*
* 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 android.support.v4.widget;
import android.content.Context;
import android.os.Build;
import android.view.View;
/**
* Helper for accessing features in {@link android.widget.SearchView}
* introduced after API level 4 in a backwards compatible fashion.
*/
public class SearchViewCompat {
interface SearchViewCompatImpl {
View newSearchView(Context context);
Object newOnQueryTextListener(OnQueryTextListenerCompat listener);
void setOnQueryTextListener(Object searchView, Object listener);
}
static class SearchViewCompatStubImpl implements SearchViewCompatImpl {
@Override
public View newSearchView(Context context) {
return null;
}
@Override
public Object newOnQueryTextListener(OnQueryTextListenerCompat listener) {
return null;
}
@Override
public void setOnQueryTextListener(Object searchView, Object listener) {
}
}
static class SearchViewCompatHoneycombImpl extends SearchViewCompatStubImpl {
@Override
public View newSearchView(Context context) {
return SearchViewCompatHoneycomb.newSearchView(context);
}
@Override
public Object newOnQueryTextListener(final OnQueryTextListenerCompat listener) {
return SearchViewCompatHoneycomb.newOnQueryTextListener(
new SearchViewCompatHoneycomb.OnQueryTextListenerCompatBridge() {
@Override
public boolean onQueryTextSubmit(String query) {
return listener.onQueryTextSubmit(query);
}
@Override
public boolean onQueryTextChange(String newText) {
return listener.onQueryTextChange(newText);
}
});
}
@Override
public void setOnQueryTextListener(Object searchView, Object listener) {
SearchViewCompatHoneycomb.setOnQueryTextListener(searchView, listener);
}
}
private static final SearchViewCompatImpl IMPL;
static {
if (Build.VERSION.SDK_INT >= 11) { // Honeycomb
IMPL = new SearchViewCompatHoneycombImpl();
} else {
IMPL = new SearchViewCompatStubImpl();
}
}
private SearchViewCompat(Context context) {
/* Hide constructor */
}
/**
* Creates a new SearchView.
*
* @param context The Context the view is running in.
* @return A SearchView instance if the class is present on the current
* platform, null otherwise.
*/
public static View newSearchView(Context context) {
return IMPL.newSearchView(context);
}
/**
* Sets a listener for user actions within the SearchView.
*
* @param searchView The SearchView in which to register the listener.
* @param listener the listener object that receives callbacks when the user performs
* actions in the SearchView such as clicking on buttons or typing a query.
*/
public static void setOnQueryTextListener(View searchView, OnQueryTextListenerCompat listener) {
IMPL.setOnQueryTextListener(searchView, listener.mListener);
}
/**
* Callbacks for changes to the query text.
*/
public static abstract class OnQueryTextListenerCompat {
final Object mListener;
public OnQueryTextListenerCompat() {
mListener = IMPL.newOnQueryTextListener(this);
}
/**
* Called when the user submits the query. This could be due to a key press on the
* keyboard or due to pressing a submit button.
* The listener can override the standard behavior by returning true
* to indicate that it has handled the submit request. Otherwise return false to
* let the SearchView handle the submission by launching any associated intent.
*
* @param query the query text that is to be submitted
*
* @return true if the query has been handled by the listener, false to let the
* SearchView perform the default action.
*/
public boolean onQueryTextSubmit(String query) {
return false;
}
/**
* Called when the query text is changed by the user.
*
* @param newText the new content of the query text field.
*
* @return false if the SearchView should perform the default action of showing any
* suggestions if available, true if the action was handled by the listener.
*/
public boolean onQueryTextChange(String newText) {
return false;
}
}
}

@ -0,0 +1,55 @@
/*
* 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 android.support.v4.widget;
import android.content.Context;
import android.view.View;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
/**
* Implementation of SearchView compatibility that can call Honeycomb APIs.
*/
class SearchViewCompatHoneycomb {
interface OnQueryTextListenerCompatBridge {
public boolean onQueryTextSubmit(String query);
public boolean onQueryTextChange(String newText);
}
public static View newSearchView(Context context) {
return new SearchView(context);
}
public static Object newOnQueryTextListener(final OnQueryTextListenerCompatBridge listener) {
return new OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return listener.onQueryTextSubmit(query);
}
@Override
public boolean onQueryTextChange(String newText) {
return listener.onQueryTextChange(newText);
}
};
}
public static void setOnQueryTextListener(Object searchView, Object listener) {
((SearchView) searchView).setOnQueryTextListener((OnQueryTextListener) listener);
}
}

@ -21,25 +21,25 @@ import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.app.ActionBar;
import android.support.v4.view.ActionMode;
import android.support.v4.view.MenuItem;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.SpinnerAdapter;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.view.menu.ActionMenuItemView;
import com.actionbarsherlock.internal.view.menu.MenuBuilder;
import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
import com.actionbarsherlock.internal.view.menu.MenuPresenter;
import com.actionbarsherlock.internal.widget.ActionBarContainer;
import com.actionbarsherlock.internal.widget.ActionBarView;
public final class ActionBarImpl extends ActionBar {
private final Activity mActivity;
private Context mContext;
/** Action bar container. */
private ActionBarContainer mContainerView;
@ -56,7 +56,9 @@ public final class ActionBarImpl extends ActionBar {
public ActionBarImpl(Activity activity) {
mActivity = activity;
Window window = activity.getWindow();
View decor = window.getDecorView();
init(decor);
}
@ -64,91 +66,21 @@ public final class ActionBarImpl extends ActionBar {
// ACTION BAR SHERLOCK SUPPORT
// ------------------------------------------------------------------------
@Override
protected ActionBar getPublicInstance() {
return this;
}
public void init() {
mActionView = (ActionBarView)mActivity.findViewById(R.id.abs__action_bar);
mContainerView = (ActionBarContainer)mActivity.findViewById(R.id.abs__action_bar_container);
private void init(View decor) {
mContext = decor.getContext();
mActionView = (ActionBarView)decor.findViewById(R.id.abs__action_bar);
mContainerView = (ActionBarContainer)decor.findViewById(R.id.abs__action_bar_container);
if (mActionView == null) {
if (mActionView == null || mContainerView == null) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used with a screen_*.xml layout");
}
mFadeInAnimation = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in);
mFadeOutAnimation = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out);
if (mActionView.getTitle() == null) {
mActionView.setTitle(mActivity.getTitle());
}
mFadeInAnimation = AnimationUtils.loadAnimation(mContext, android.R.anim.fade_in);
mFadeOutAnimation = AnimationUtils.loadAnimation(mContext, android.R.anim.fade_out);
}
public void onMenuInflated(MenuBuilder menu) {
if (mActionView == null) {
return;
}
final int maxItems = mActivity.getResources().getInteger(R.integer.abs__max_action_buttons);
//Iterate and grab as many actions as we can up to maxItems honoring
//their showAsAction values
int ifItems = 0;
final int count = menu.size();
boolean showsActionItemText = menu.getShowsActionItemText();
List<MenuItemImpl> keep = new ArrayList<MenuItemImpl>();
for (int i = 0; i < count; i++) {
MenuItemImpl item = (MenuItemImpl)menu.getItem(i);
//Items without an icon or custom view are forced into the overflow menu
if (!showsActionItemText && (item.getIcon() == null) && (item.getActionView() == null)) {
continue;
}
if (showsActionItemText && ((item.getTitle() == null) || "".equals(item.getTitle()))) {
continue;
}
if ((item.getShowAsAction() & MenuItem.SHOW_AS_ACTION_ALWAYS) != 0) {
//Show always therefore add to keep list
keep.add(item);
if ((keep.size() > maxItems) && (ifItems > 0)) {
//If we have exceeded the max and there are "ifRoom" items
//then iterate backwards to remove one and add it to the
//head of the classic items list.
for (int j = keep.size() - 1; j >= 0; j--) {
if ((keep.get(j).getShowAsAction() & MenuItem.SHOW_AS_ACTION_IF_ROOM) != 0) {
keep.remove(j);
ifItems -= 1;
break;
}
}
}
} else if (((item.getShowAsAction() & MenuItem.SHOW_AS_ACTION_IF_ROOM) != 0)
&& (keep.size() < maxItems)) {
//"ifRoom" items are added if we have not exceeded the max.
keep.add(item);
ifItems += 1;
}
}
//Mark items that will be shown on the action bar as such so they do
//not show up on the activity options menu
mActionView.removeAllItems();
for (MenuItemImpl item : keep) {
item.setIsShownOnActionBar(true);
//Get a new item for this menu item
ActionMenuItemView actionItem = mActionView.newItem();
actionItem.initialize(item, MenuBuilder.TYPE_ACTION_BAR);
//Associate the itemview with the item so changes will be reflected
item.setItemView(MenuBuilder.TYPE_ACTION_BAR, actionItem);
//Add to the action bar for display
mActionView.addItem(actionItem);
}
public void setMenu(MenuBuilder menu, MenuPresenter.Callback cb) {
mActionView.setMenu(menu, cb);
}
public void onMenuVisibilityChanged(boolean isVisible) {
@ -158,12 +90,6 @@ public final class ActionBarImpl extends ActionBar {
}
}
public void setProgressBarIndeterminateVisibility(boolean visible) {
if (mActionView != null) {
mActionView.setProgressBarIndeterminateVisibility(visible);
}
}
// ------------------------------------------------------------------------
// ACTION MODE METHODS
// ------------------------------------------------------------------------
@ -328,7 +254,7 @@ public final class ActionBarImpl extends ActionBar {
@Override
public void setCustomView(int resId) {
View view = LayoutInflater.from(mActivity).inflate(resId, mActionView, false);
View view = LayoutInflater.from(mContext).inflate(resId, mActionView, false);
setCustomView(view);
}

@ -17,6 +17,8 @@
package com.actionbarsherlock.internal.app;
import java.util.HashMap;
import com.actionbarsherlock.internal.view.menu.MenuInflaterImpl;
import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
import com.actionbarsherlock.internal.view.menu.MenuWrapper;
import android.app.Activity;
@ -25,7 +27,6 @@ import android.graphics.drawable.Drawable;
import android.support.v4.app.ActionBar;
import android.support.v4.view.ActionMode;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuInflater;
import android.view.View;
import android.widget.SpinnerAdapter;
@ -70,11 +71,6 @@ public final class ActionBarWrapper {
return mActivity.getActionBar();
}
@Override
protected ActionBar getPublicInstance() {
return (getActionBar() != null) ? this : null;
}
/**
* Converts our Tab wrapper to a native version containing the wrapper
* instance as its tag.
@ -179,8 +175,8 @@ public final class ActionBarWrapper {
}
@Override
public MenuInflater getMenuInflater() {
return new MenuInflater(mContext, null);
public MenuInflaterImpl getMenuInflater() {
return new MenuInflaterImpl(mContext, null);
}
@Override

@ -0,0 +1,6 @@
package com.actionbarsherlock.internal.view;
public interface View_HasStateListenerSupport {
void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener);
void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener);
}

@ -0,0 +1,8 @@
package com.actionbarsherlock.internal.view;
import android.view.View;
public interface View_OnAttachStateChangeListener {
void onViewAttachedToWindow(View v);
void onViewDetachedFromWindow(View v);
}

@ -0,0 +1,263 @@
/*
* Copyright (C) 2010 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 java.util.List;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
import android.support.v4.view.SubMenu;
import android.view.KeyEvent;
/**
* @hide
*/
public class ActionMenu implements Menu {
private Context mContext;
private boolean mIsQwerty;
private ArrayList<ActionMenuItem> mItems;
public ActionMenu(Context context) {
mContext = context;
mItems = new ArrayList<ActionMenuItem>();
}
public Context getContext() {
return mContext;
}
public MenuItem add(CharSequence title) {
return add(0, 0, 0, title);
}
public MenuItem add(int titleRes) {
return add(0, 0, 0, titleRes);
}
public MenuItem add(int groupId, int itemId, int order, int titleRes) {
return add(groupId, itemId, order, mContext.getResources().getString(titleRes));
}
public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
ActionMenuItem item = new ActionMenuItem(getContext(),
groupId, itemId, 0, order, title);
mItems.add(order, item);
return item;
}
public int addIntentOptions(int groupId, int itemId, int order,
ComponentName caller, Intent[] specifics, Intent intent, int flags,
android.view.MenuItem[] outSpecificItems) {
PackageManager pm = mContext.getPackageManager();
final List<ResolveInfo> lri =
pm.queryIntentActivityOptions(caller, specifics, intent, 0);
final int N = lri != null ? lri.size() : 0;
if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
removeGroup(groupId);
}
for (int i=0; i<N; i++) {
final ResolveInfo ri = lri.get(i);
Intent rintent = new Intent(
ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
rintent.setComponent(new ComponentName(
ri.activityInfo.applicationInfo.packageName,
ri.activityInfo.name));
final MenuItem item = add(groupId, itemId, order, ri.loadLabel(pm))
.setIcon(ri.loadIcon(pm))
.setIntent(rintent);
if (outSpecificItems != null && ri.specificIndex >= 0) {
outSpecificItems[ri.specificIndex] = item;
}
}
return N;
}
public SubMenu addSubMenu(CharSequence title) {
// TODO Implement submenus
return null;
}
public SubMenu addSubMenu(int titleRes) {
// TODO Implement submenus
return null;
}
public SubMenu addSubMenu(int groupId, int itemId, int order,
CharSequence title) {
// TODO Implement submenus
return null;
}
public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
// TODO Implement submenus
return null;
}
public void clear() {
mItems.clear();
}
public void close() {
}
private int findItemIndex(int id) {
final ArrayList<ActionMenuItem> items = mItems;
final int itemCount = items.size();
for (int i = 0; i < itemCount; i++) {
if (items.get(i).getItemId() == id) {
return i;
}
}
return -1;
}
public MenuItem findItem(int id) {
return mItems.get(findItemIndex(id));
}
public MenuItem getItem(int index) {
return mItems.get(index);
}
public boolean hasVisibleItems() {
final ArrayList<ActionMenuItem> items = mItems;
final int itemCount = items.size();
for (int i = 0; i < itemCount; i++) {
if (items.get(i).isVisible()) {
return true;
}
}
return false;
}
private ActionMenuItem findItemWithShortcut(int keyCode, KeyEvent event) {
// TODO Make this smarter.
final boolean qwerty = mIsQwerty;
final ArrayList<ActionMenuItem> items = mItems;
final int itemCount = items.size();
for (int i = 0; i < itemCount; i++) {
ActionMenuItem item = items.get(i);
final char shortcut = qwerty ? item.getAlphabeticShortcut() :
item.getNumericShortcut();
if (keyCode == shortcut) {
return item;
}
}
return null;
}
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return findItemWithShortcut(keyCode, event) != null;
}
public boolean performIdentifierAction(int id, int flags) {
final int index = findItemIndex(id);
if (index < 0) {
return false;
}
return mItems.get(index).invoke();
}
public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
ActionMenuItem item = findItemWithShortcut(keyCode, event);
if (item == null) {
return false;
}
return item.invoke();
}
public void removeGroup(int groupId) {
final ArrayList<ActionMenuItem> items = mItems;
int itemCount = items.size();
int i = 0;
while (i < itemCount) {
if (items.get(i).getGroupId() == groupId) {
items.remove(i);
itemCount--;
} else {
i++;
}
}
}
public void removeItem(int id) {
mItems.remove(findItemIndex(id));
}
public void setGroupCheckable(int group, boolean checkable,
boolean exclusive) {
final ArrayList<ActionMenuItem> items = mItems;
final int itemCount = items.size();
for (int i = 0; i < itemCount; i++) {
ActionMenuItem item = items.get(i);
if (item.getGroupId() == group) {
item.setCheckable(checkable);
item.setExclusiveCheckable(exclusive);
}
}
}
public void setGroupEnabled(int group, boolean enabled) {
final ArrayList<ActionMenuItem> items = mItems;
final int itemCount = items.size();
for (int i = 0; i < itemCount; i++) {
ActionMenuItem item = items.get(i);
if (item.getGroupId() == group) {
item.setEnabled(enabled);
}
}
}
public void setGroupVisible(int group, boolean visible) {
final ArrayList<ActionMenuItem> items = mItems;
final int itemCount = items.size();
for (int i = 0; i < itemCount; i++) {
ActionMenuItem item = items.get(i);
if (item.getGroupId() == group) {
item.setVisible(visible);
}
}
}
public void setQwertyMode(boolean isQwerty) {
mIsQwerty = isQwerty;
}
public int size() {
return mItems.size();
}
}

@ -1,3 +1,19 @@
/*
* Copyright (C) 2010 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;
@ -5,251 +21,229 @@ import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.view.MenuItem;
import android.support.v4.view.SubMenu;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View;
/**
* @hide
*/
public class ActionMenuItem implements MenuItem {
private static final int CHECKABLE = MenuItemImpl.CHECKABLE;
private static final int CHECKED = MenuItemImpl.CHECKED;
private static final int ENABLED = MenuItemImpl.ENABLED;
private static final int EXCLUSIVE = MenuItemImpl.EXCLUSIVE;
private static final int HIDDEN = MenuItemImpl.HIDDEN;
private static final int NO_ICON = 0;
//XXX UNUSED: private final int mCategoryOrder;
private MenuItem.OnMenuItemClickListener mClickListener;
private Context mContext;
private int mFlags = ENABLED;
private final int mGroup;
private Drawable mIconDrawable;
private int mIconResId = NO_ICON;
private final int mId;
private Intent mIntent;
private final int mGroup;
//UNUSED private final int mCategoryOrder;
private final int mOrdering;
private char mShortcutAlphabeticChar;
private char mShortcutNumericChar;
private CharSequence mTitle;
private CharSequence mTitleCondensed;
private Intent mIntent;
private char mShortcutNumericChar;
private char mShortcutAlphabeticChar;
private Drawable mIconDrawable;
//UNUSED private int mIconResId = NO_ICON;
public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering, CharSequence title) {
private Context mContext;
private MenuItem.OnMenuItemClickListener mClickListener;
//UNUSED private static final int NO_ICON = 0;
private int mFlags = ENABLED;
private static final int CHECKABLE = 0x00000001;
private static final int CHECKED = 0x00000002;
private static final int EXCLUSIVE = 0x00000004;
private static final int HIDDEN = 0x00000008;
private static final int ENABLED = 0x00000010;
public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering,
CharSequence title) {
mContext = context;
mId = id;
mGroup = group;
//XXX UNUSED mCategoryOrder = categoryOrder;
//UNUSED mCategoryOrder = categoryOrder;
mOrdering = ordering;
mTitle = title;
}
@Override
public View getActionView() {
return null;
}
@Override
public char getAlphabeticShortcut() {
return mShortcutAlphabeticChar;
}
@Override
public int getGroupId() {
return mGroup;
}
@Override
public Drawable getIcon() {
return mIconDrawable;
}
@Override
public Intent getIntent() {
return mIntent;
}
@Override
public int getItemId() {
return mId;
}
@Override
public ContextMenu.ContextMenuInfo getMenuInfo() {
public ContextMenuInfo getMenuInfo() {
return null;
}
@Override
public char getNumericShortcut() {
return mShortcutNumericChar;
}
@Override
public int getOrder() {
return mOrdering;
}
@Override
public SubMenu getSubMenu() {
return null;
}
@Override
public CharSequence getTitle() {
return mTitle;
}
@Override
public CharSequence getTitleCondensed() {
return mTitleCondensed;
}
@Override
public boolean hasSubMenu() {
return false;
}
public boolean invoke() {
if ((mClickListener != null) && mClickListener.onMenuItemClick(this)) {
return true;
} else if (mIntent != null) {
mContext.startActivity(mIntent);
return true;
}
return false;
}
@Override
public boolean isCheckable() {
return (mFlags & CHECKABLE) != 0;
}
@Override
public boolean isChecked() {
return (mFlags & CHECKED) != 0;
}
@Override
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
@Override
public boolean isVisible() {
return (mFlags & HIDDEN) == 0;
}
@Override
public MenuItem setActionView(int layoutResId) {
throw new UnsupportedOperationException();
}
@Override
public MenuItem setActionView(View view) {
throw new UnsupportedOperationException();
}
@Override
public MenuItem setAlphabeticShortcut(char shortcut) {
mShortcutAlphabeticChar = shortcut;
public MenuItem setAlphabeticShortcut(char alphaChar) {
mShortcutAlphabeticChar = alphaChar;
return this;
}
@Override
public MenuItem setCheckable(boolean checkable) {
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
return this;
}
@Override
public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
return this;
}
public MenuItem setChecked(boolean checked) {
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
return this;
}
@Override
public MenuItem setEnabled(boolean enabled) {
mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
return this;
}
public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
return this;
}
@Override
public MenuItem setIcon(int resId) {
mIconResId = resId;
mIconDrawable = mContext.getResources().getDrawable(mIconResId);
public MenuItem setIcon(Drawable icon) {
mIconDrawable = icon;
//UNUSED mIconResId = NO_ICON;
return this;
}
@Override
public MenuItem setIcon(Drawable icon) {
mIconDrawable = icon;
mIconResId = NO_ICON;
public MenuItem setIcon(int iconRes) {
//UNUSED mIconResId = iconRes;
mIconDrawable = mContext.getResources().getDrawable(iconRes);
return this;
}
@Override
public MenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
}
@Override
public MenuItem setNumericShortcut(char shortcut) {
mShortcutNumericChar = shortcut;
public MenuItem setNumericShortcut(char numericChar) {
mShortcutNumericChar = numericChar;
return this;
}
@Override
public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
mClickListener = listener;
public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
mClickListener = menuItemClickListener;
return this;
}
@Override
public android.view.MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener listener) {
mClickListener = new MenuItem.OnMenuItemClickListener() {
public MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener menuItemClickListener) {
mClickListener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return listener.onMenuItemClick(item);
return menuItemClickListener.onMenuItemClick(item);
}
};
return null;
}
@Override
public MenuItem setShortcut(char numericShortcut, char alphabeticShortcut) {
mShortcutNumericChar = numericShortcut;
mShortcutAlphabeticChar = alphabeticShortcut;
return this;
}
@Override
public void setShowAsAction(int layoutResId) {
//No op
}
@Override
public MenuItem setTitle(int resId) {
this.mTitle = mContext.getResources().getString(resId);
public MenuItem setShortcut(char numericChar, char alphaChar) {
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = alphaChar;
return this;
}
@Override
public MenuItem setTitle(CharSequence title) {
mTitle = title;
return this;
}
@Override
public MenuItem setTitle(int title) {
mTitle = mContext.getResources().getString(title);
return this;
}
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
return this;
}
@Override
public MenuItem setVisible(boolean visible) {
mFlags = (mFlags & HIDDEN) | (visible ? 0 : HIDDEN);
return this;
}
}
public boolean invoke() {
if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
return true;
}
if (mIntent != null) {
mContext.startActivity(mIntent);
return true;
}
return false;
}
public void setShowAsAction(int show) {
// Do nothing. ActionMenuItems always show as action buttons.
}
public MenuItem setActionView(View actionView) {
throw new UnsupportedOperationException();
}
public View getActionView() {
return null;
}
@Override
public MenuItem setActionView(int resId) {
throw new UnsupportedOperationException();
}
}

@ -1,149 +1,245 @@
/*
* Copyright (C) 2010 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.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.view.View_HasStateListenerSupport;
import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener;
/**
* @hide
*/
public class ActionMenuItemView extends LinearLayout
implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
ActionMenuView.ActionMenuChildView, View_HasStateListenerSupport {
//UNUSED private static final String TAG = "ActionMenuItemView";
private MenuItemImpl mItemData;
private CharSequence mTitle;
private MenuBuilder.ItemInvoker mItemInvoker;
private ImageButton mImageButton;
private Button mTextButton;
private boolean mAllowTextWithIcon;
//UNUSED private boolean mShowTextAllCaps;
private boolean mExpandedFormat;
public class ActionMenuItemView extends RelativeLayout implements MenuView.ItemView, View.OnClickListener {
private ImageView mImageButton;
private TextView mTextButton;
private FrameLayout mCustomView;
private MenuItemImpl mMenuItem;
private WeakReference<ImageView> mDivider;
private final Set<View_OnAttachStateChangeListener> mListeners = new HashSet<View_OnAttachStateChangeListener>();
public ActionMenuItemView(Context context) {
this(context, null);
}
public ActionMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.actionButtonStyle);
this(context, attrs, 0);
}
public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOnClickListener(this);
//TODO super(context, attrs, defStyle);
super(context, attrs);
final Resources res = context.getResources();
mAllowTextWithIcon = res.getBoolean(
R.bool.abs__config_allowActionMenuItemTextWithIcon);
//UNUSED mShowTextAllCaps = res.getBoolean(R.bool.abs__config_actionMenuItemAllCaps);
}
@Override
public void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
mListeners.add(listener);
}
@Override
public void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
mListeners.remove(listener);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
protected void onAttachedToWindow() {
super.onAttachedToWindow();
for (View_OnAttachStateChangeListener listener : mListeners) {
listener.onViewAttachedToWindow(this);
}
}
mImageButton = (ImageView) findViewById(R.id.abs__item_icon);
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
for (View_OnAttachStateChangeListener listener : mListeners) {
listener.onViewDetachedFromWindow(this);
}
}
@Override
public void onFinishInflate() {
mImageButton = (ImageButton) findViewById(R.id.abs__imageButton);
mTextButton = (Button) findViewById(R.id.abs__textButton);
mImageButton.setOnClickListener(this);
mTextButton = (TextView) findViewById(R.id.abs__item_text);
mTextButton.setOnClickListener(this);
mCustomView = (FrameLayout) findViewById(R.id.abs__item_custom);
mCustomView.setOnClickListener(this);
mImageButton.setOnLongClickListener(this);
setOnClickListener(this);
setOnLongClickListener(this);
}
public MenuItemImpl getItemData() {
return mItemData;
}
public void initialize(MenuItemImpl itemData, int menuType) {
mItemData = itemData;
setIcon(itemData.getIcon());
setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
setId(itemData.getItemId());
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
setEnabled(itemData.isEnabled());
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mImageButton.setEnabled(enabled);
mTextButton.setEnabled(enabled);
mCustomView.setEnabled(enabled);
}
public void setDivider(ImageView divider) {
mDivider = new WeakReference<ImageView>(divider);
//Ensure we are not displaying the divider when we are not visible
setDividerVisibility(getVisibility());
public void onClick(View v) {
if (mItemInvoker != null) {
mItemInvoker.invokeItem(mItemData);
}
}
public void setVisible(boolean visible) {
final int visibility = visible ? View.VISIBLE : View.GONE;
setDividerVisibility(visibility);
setVisibility(visibility);
public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
mItemInvoker = invoker;
}
private void setDividerVisibility(int visibility) {
if ((mDivider != null) && (mDivider.get() != null)) {
mDivider.get().setVisibility(visibility);
}
public boolean prefersCondensedTitle() {
return true;
}
public void reloadDisplay() {
final boolean hasCustomView = mCustomView.getChildCount() > 0;
final boolean hasText = mMenuItem.showsActionItemText() && !"".equals(mTextButton.getText());
if (hasCustomView) {
mCustomView.setVisibility(View.VISIBLE);
mImageButton.setVisibility(View.GONE);
mTextButton.setVisibility(View.GONE);
} else {
mCustomView.setVisibility(View.GONE);
mImageButton.setVisibility(View.VISIBLE);
mTextButton.setVisibility(hasText ? View.VISIBLE : View.GONE);
}
public void setCheckable(boolean checkable) {
// TODO Support checkable action items
}
public void setIcon(Drawable icon) {
mImageButton.setImageDrawable(icon);
public void setChecked(boolean checked) {
// TODO Support checkable action items
}
public void setTitle(CharSequence title) {
mTextButton.setText(title);
reloadDisplay();
public void setExpandedFormat(boolean expandedFormat) {
if (mExpandedFormat != expandedFormat) {
mExpandedFormat = expandedFormat;
if (mItemData != null) {
mItemData.actionFormatChanged();
}
}
}
@Override
public void initialize(MenuItemImpl itemData, int menuType) {
mMenuItem = itemData;
setId(itemData.getItemId());
setIcon(itemData.getIcon());
setTitle(itemData.getTitle());
setEnabled(itemData.isEnabled());
setActionView(itemData.getActionView());
setVisible(itemData.isVisible());
}
private void updateTextButtonVisibility() {
boolean visible = !TextUtils.isEmpty(mTextButton.getText());
visible &= mImageButton.getDrawable() == null ||
(mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
@Override
public MenuItemImpl getItemData() {
return mMenuItem;
mTextButton.setVisibility(visible ? VISIBLE : GONE);
}
@Override
public void setCheckable(boolean checkable) {
// No-op
public void setIcon(Drawable icon) {
mImageButton.setImageDrawable(icon);
if (icon != null) {
mImageButton.setVisibility(VISIBLE);
} else {
mImageButton.setVisibility(GONE);
}
updateTextButtonVisibility();
}
@Override
public void setChecked(boolean checked) {
// No-op
public boolean hasText() {
return mTextButton.getVisibility() != GONE;
}
@Override
public void setShortcut(boolean showShortcut, char shortcutKey) {
// No-op
// Action buttons don't show text for shortcut keys.
}
@Override
public void setActionView(View actionView) {
mCustomView.removeAllViews();
if (actionView != null) {
mCustomView.addView(actionView);
}
reloadDisplay();
}
public void setTitle(CharSequence title) {
mTitle = title;
@Override
public boolean prefersCondensedTitle() {
return true;
mTextButton.setText(mTitle);
setContentDescription(mTitle);
updateTextButtonVisibility();
}
@Override
public boolean showsIcon() {
return true;
}
public boolean needsDividerBefore() {
return hasText() && mItemData.getIcon() == null;
}
public boolean needsDividerAfter() {
return hasText();
}
@Override
public void onClick(View v) {
if (mMenuItem != null) {
mMenuItem.invoke();
public boolean onLongClick(View v) {
if (hasText()) {
// Don't show the cheat sheet for items that already show text.
return false;
}
final int[] screenPos = new int[2];
final Rect displayFrame = new Rect();
getLocationOnScreen(screenPos);
getWindowVisibleDisplayFrame(displayFrame);
final Context context = getContext();
final int width = getWidth();
final int height = getHeight();
final int midy = screenPos[1] + height / 2;
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
if (midy < displayFrame.height()) {
// Show along the top; follow action buttons
cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT,
screenWidth - screenPos[0] - width / 2, height);
} else {
// Show along the bottom center
cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
}
cheatSheet.show();
return true;
}
}

@ -0,0 +1,448 @@
/*
* 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 java.util.List;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.MenuItem;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import com.actionbarsherlock.R;
/**
* MenuPresenter for building action menus as seen in the action bar and action modes.
*/
public class ActionMenuPresenter extends BaseMenuPresenter {
//UNUSED private static final String TAG = "ActionMenuPresenter";
private int mWidthLimit;
private int mActionItemWidthLimit;
private int mMaxItems;
private boolean mMaxItemsSet;
private boolean mStrictWidthLimit;
private boolean mWidthLimitSet;
private int mMinCellSize;
private AlertDialog mDialog;
// Group IDs that have been added as actions - used temporarily, allocated here for reuse.
private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
private View mScrapActionButtonView;
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 (!mWidthLimitSet) {
mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
}
// Measure for initial configuration
if (!mMaxItemsSet) {
mMaxItems = res.getInteger(R.integer.abs__max_action_buttons);
}
mActionItemWidthLimit = mWidthLimit;
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 void onConfigurationChanged(Configuration newConfig) {
if (!mMaxItemsSet) {
mMaxItems = mContext.getResources().getInteger(
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 setItemLimit(int itemCount) {
mMaxItems = itemCount;
mMaxItemsSet = true;
}
@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) {
if (!(convertView instanceof ActionMenuItemView)) {
convertView = null;
}
actionView = super.getItemView(item, convertView, parent);
}
actionView.setVisibility(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 boolean filterLeftoverView(ViewGroup parent, int childIndex) {
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) {
return false;
}
mOpenSubMenuId = subMenu.getItem().getItemId();
final List<MenuItemImpl> items = subMenu.getVisibleItems();
final int itemsSize = items.size();
final CharSequence[] itemText = new CharSequence[itemsSize];
for (int i = 0; i < itemsSize; i++) {
itemText[i] = items.get(i).getTitle();
}
AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
.setTitle(subMenu.getItem().getTitle())
.setIcon(subMenu.getItem().getIcon())
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
mDialog = null;
}
});
final boolean isExclusive = ((MenuItemImpl)subMenu.getItem(0)).isExclusiveCheckable();
final boolean isCheckable = ((MenuItemImpl)subMenu.getItem(0)).isCheckable();
if (isExclusive) {
int selected = -1;
for (int i = 0; i < itemsSize; i++) {
if (items.get(i).isChecked()) {
selected = i;
break;
}
}
builder.setSingleChoiceItems(itemText, selected, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
items.get(which).invoke();
//dialog.dismiss();
mDialog = null;
}
});
} else if (isCheckable) {
boolean[] selected = new boolean[itemsSize];
for (int i = 0; i < itemsSize; i++) {
selected[i] = items.get(i).isChecked();
}
builder.setMultiChoiceItems(itemText, selected, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
items.get(which).setChecked(isChecked);
//dialog.dismiss();
mDialog = null;
}
});
} else {
builder.setItems(itemText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
items.get(which).invoke();
//dialog.dismiss();
mDialog = null;
}
});
}
mDialog = builder.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;
}
/**
* 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() {
return hideSubMenus();
}
/**
* Dismiss all submenu popups.
*
* @return true if popups were dismissed, false otherwise. (This can be because none were open.)
*/
public boolean hideSubMenus() {
if (mDialog != null) {
try {
mDialog.dismiss();
} catch (Exception e) {
//Must have been dismissed or cancelled already
}
mDialog = null;
return true;
}
return false;
}
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;
for (int i = 0; i < itemsSize; i++) {
MenuItemImpl item = visibleItems.get(i);
if (item.requiresActionButton()) {
requiredItems++;
}/* else if (item.requestsActionButton()) {
requestedItems++;
}*/
}
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);
}
}
}
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];
}
};
}
}

@ -0,0 +1,551 @@
/*
* Copyright (C) 2010 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 com.actionbarsherlock.internal.widget.IcsLinearLayout;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
* @hide
*/
public class ActionMenuView extends IcsLinearLayout implements MenuBuilder.ItemInvoker, MenuView {
//UNUSED private static final String TAG = "ActionMenuView";
static final int MIN_CELL_SIZE = 56; // dips
static final int GENERATED_ITEM_PADDING = 4; // dips
private MenuBuilder mMenu;
private boolean mReserveOverflow;
private ActionMenuPresenter mPresenter;
private boolean mFormatItems;
private int mFormatItemsWidth;
private int mMinCellSize;
private int mGeneratedItemPadding;
//UNUSED private int mMeasuredExtraWidth;
public ActionMenuView(Context context) {
this(context, null);
}
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
setBaselineAligned(false);
final float density = context.getResources().getDisplayMetrics().density;
mMinCellSize = (int) (MIN_CELL_SIZE * density);
mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
}
public void setPresenter(ActionMenuPresenter presenter) {
mPresenter = presenter;
}
public boolean isExpandedFormat() {
return mFormatItems;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
super.onConfigurationChanged(newConfig);
//TODO figure out a way to call this pre-2.2
}
mPresenter.updateMenuView(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If we've been given an exact size to match, apply special formatting during layout.
final boolean wasFormatted = mFormatItems;
mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
if (wasFormatted != mFormatItems) {
mFormatItemsWidth = 0; // Reset this when switching modes
}
// Special formatting can change whether items can fit as action buttons.
// Kick the menu and update presenters when this changes.
final int widthSize = MeasureSpec.getMode(widthMeasureSpec);
if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
mFormatItemsWidth = widthSize;
mMenu.onItemsChanged(true);
}
if (mFormatItems) {
onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
// We already know the width mode is EXACTLY if we're here.
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthPadding = getPaddingLeft() + getPaddingRight();
final int heightPadding = getPaddingTop() + getPaddingBottom();
widthSize -= widthPadding;
// Divide the view into cells.
final int cellCount = widthSize / mMinCellSize;
final int cellSizeRemaining = widthSize % mMinCellSize;
if (cellCount == 0) {
// Give up, nothing fits.
setMeasuredDimension(widthSize, 0);
return;
}
final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
int cellsRemaining = cellCount;
int maxChildHeight = 0;
int maxCellsUsed = 0;
int expandableItemCount = 0;
int visibleItemCount = 0;
boolean hasOverflow = false;
// This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
long smallestItemsAt = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) continue;
final boolean isGeneratedItem = child instanceof ActionMenuItemView;
visibleItemCount++;
if (isGeneratedItem) {
// Reset padding for generated menu item views; it may change below
// and views are recycled.
child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.expanded = false;
lp.extraPixels = 0;
lp.cellsUsed = 0;
lp.expandable = false;
lp.leftMargin = 0;
lp.rightMargin = 0;
lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
// Overflow always gets 1 cell. No more, no less.
final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
heightMeasureSpec, heightPadding);
maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
if (lp.expandable) expandableItemCount++;
if (lp.isOverflowButton) hasOverflow = true;
cellsRemaining -= cellsUsed;
maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
if (cellsUsed == 1) smallestItemsAt |= (1 << i);
}
// When we have overflow and a single expanded (text) item, we want to try centering it
// visually in the available space even though overflow consumes some of it.
final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
// Divide space for remaining cells if we have items that can expand.
// Try distributing whole leftover cells to smaller items first.
boolean needsExpansion = false;
while (expandableItemCount > 0 && cellsRemaining > 0) {
int minCells = Integer.MAX_VALUE;
long minCellsAt = 0; // Bit locations are indices of relevant child views
int minCellsItemCount = 0;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't try to expand items that shouldn't.
if (!lp.expandable) continue;
// Mark indices of children that can receive an extra cell.
if (lp.cellsUsed < minCells) {
minCells = lp.cellsUsed;
minCellsAt = 1 << i;
minCellsItemCount = 1;
} else if (lp.cellsUsed == minCells) {
minCellsAt |= 1 << i;
minCellsItemCount++;
}
}
// Items that get expanded will always be in the set of smallest items when we're done.
smallestItemsAt |= minCellsAt;
if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
// We have enough cells, all minimum size items will be incremented.
minCells++;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if ((minCellsAt & (1 << i)) == 0) {
// If this item is already at our small item count, mark it for later.
if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
continue;
}
if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
// Add padding to this item such that it centers.
child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
}
lp.cellsUsed++;
lp.expanded = true;
cellsRemaining--;
}
needsExpansion = true;
}
// Divide any space left that wouldn't divide along cell boundaries
// evenly among the smallest items
final boolean singleItem = !hasOverflow && visibleItemCount == 1;
if (cellsRemaining > 0 && smallestItemsAt != 0 &&
(cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
float expandCount = Long.bitCount(smallestItemsAt);
if (!singleItem) {
// The items at the far edges may only expand by half in order to pin to either side.
if ((smallestItemsAt & 1) != 0) {
LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
if (!lp.preventEdgeOffset) expandCount -= 0.5f;
}
if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
if (!lp.preventEdgeOffset) expandCount -= 0.5f;
}
}
final int extraPixels = expandCount > 0 ?
(int) (cellsRemaining * cellSize / expandCount) : 0;
for (int i = 0; i < childCount; i++) {
if ((smallestItemsAt & (1 << i)) == 0) continue;
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (child instanceof ActionMenuItemView) {
// If this is one of our views, expand and measure at the larger size.
lp.extraPixels = extraPixels;
lp.expanded = true;
if (i == 0 && !lp.preventEdgeOffset) {
// First item gets part of its new padding pushed out of sight.
// The last item will get this implicitly from layout.
lp.leftMargin = -extraPixels / 2;
}
needsExpansion = true;
} else if (lp.isOverflowButton) {
lp.extraPixels = extraPixels;
lp.expanded = true;
lp.rightMargin = -extraPixels / 2;
needsExpansion = true;
} else {
// If we don't know what it is, give it some margins instead
// and let it center within its space. We still want to pin
// against the edges.
if (i != 0) {
lp.leftMargin = extraPixels / 2;
}
if (i != childCount - 1) {
lp.rightMargin = extraPixels / 2;
}
}
}
cellsRemaining = 0;
}
// Remeasure any items that have had extra space allocated to them.
if (needsExpansion) {
int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - heightPadding, heightMode);
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.expanded) continue;
final int width = lp.cellsUsed * cellSize + lp.extraPixels;
child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec);
}
}
if (heightMode != MeasureSpec.EXACTLY) {
heightSize = maxChildHeight;
}
setMeasuredDimension(widthSize, heightSize);
//UNUSED mMeasuredExtraWidth = cellsRemaining * cellSize;
}
/**
* Measure a child view to fit within cell-based formatting. The child's width
* will be measured to a whole multiple of cellSize.
*
* <p>Sets the expandable and cellsUsed fields of LayoutParams.
*
* @param child Child to measure
* @param cellSize Size of one cell
* @param cellsRemaining Number of cells remaining that this view can expand to fill
* @param parentHeightMeasureSpec MeasureSpec used by the parent view
* @param parentHeightPadding Padding present in the parent view
* @return Number of cells this child was measured to occupy
*/
static int measureChildForCells(View child, int cellSize, int cellsRemaining,
int parentHeightMeasureSpec, int parentHeightPadding) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
parentHeightPadding;
final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
int cellsUsed = 0;
if (cellsRemaining > 0) {
final int childWidthSpec = MeasureSpec.makeMeasureSpec(
cellSize * cellsRemaining, MeasureSpec.AT_MOST);
child.measure(childWidthSpec, childHeightSpec);
final int measuredWidth = child.getMeasuredWidth();
cellsUsed = measuredWidth / cellSize;
if (measuredWidth % cellSize != 0) cellsUsed++;
}
final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
(ActionMenuItemView) child : null;
final boolean expandable = !lp.isOverflowButton && itemView != null && itemView.hasText();
lp.expandable = expandable;
lp.cellsUsed = cellsUsed;
final int targetWidth = cellsUsed * cellSize;
child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
childHeightSpec);
return cellsUsed;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!mFormatItems) {
super.onLayout(changed, left, top, right, bottom);
return;
}
final int childCount = getChildCount();
final int midVertical = (top + bottom) / 2;
final int dividerWidth = getDividerWidth();
int overflowWidth = 0;
//UNUSED int nonOverflowWidth = 0;
int nonOverflowCount = 0;
int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
boolean hasOverflow = false;
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
if (v.getVisibility() == GONE) {
continue;
}
LayoutParams p = (LayoutParams) v.getLayoutParams();
if (p.isOverflowButton) {
overflowWidth = v.getMeasuredWidth();
if (hasDividerBeforeChildAt(i)) {
overflowWidth += dividerWidth;
}
int height = v.getMeasuredHeight();
int r = getWidth() - getPaddingRight() - p.rightMargin;
int l = r - overflowWidth;
int t = midVertical - (height / 2);
int b = t + height;
v.layout(l, t, r, b);
widthRemaining -= overflowWidth;
hasOverflow = true;
} else {
final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
//UNUSED nonOverflowWidth += size;
widthRemaining -= size;
if (hasDividerBeforeChildAt(i)) {
//UNUSED nonOverflowWidth += dividerWidth;
}
nonOverflowCount++;
}
}
if (childCount == 1 && !hasOverflow) {
// Center a single child
final View v = getChildAt(0);
final int width = v.getMeasuredWidth();
final int height = v.getMeasuredHeight();
final int midHorizontal = (right - left) / 2;
final int l = midHorizontal - width / 2;
final int t = midVertical - height / 2;
v.layout(l, t, l + width, t + height);
return;
}
final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
int startLeft = getPaddingLeft();
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
final LayoutParams lp = (LayoutParams) v.getLayoutParams();
if (v.getVisibility() == GONE || lp.isOverflowButton) {
continue;
}
startLeft += lp.leftMargin;
int width = v.getMeasuredWidth();
int height = v.getMeasuredHeight();
int t = midVertical - height / 2;
v.layout(startLeft, t, startLeft + width, t + height);
startLeft += width + lp.rightMargin + spacerSize;
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mPresenter.dismissPopupMenus();
}
public boolean isOverflowReserved() {
return mReserveOverflow;
}
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_VERTICAL;
return params;
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
if (p instanceof LayoutParams) {
LayoutParams result = new LayoutParams((LayoutParams) p);
if (result.gravity <= Gravity.NO_GRAVITY) {
result.gravity = Gravity.CENTER_VERTICAL;
}
return result;
}
return generateDefaultLayoutParams();
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p != null && p instanceof LayoutParams;
}
public LayoutParams generateOverflowButtonLayoutParams() {
LayoutParams result = generateDefaultLayoutParams();
result.isOverflowButton = true;
return result;
}
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
public int getWindowAnimations() {
return 0;
}
public void initialize(MenuBuilder menu) {
mMenu = menu;
}
@Override
protected boolean hasDividerBeforeChildAt(int childIndex) {
final View childBefore = getChildAt(childIndex - 1);
final View child = getChildAt(childIndex);
boolean result = false;
if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
}
if (childIndex > 0 && child instanceof ActionMenuChildView) {
result |= ((ActionMenuChildView) child).needsDividerBefore();
}
return result;
}
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
return false;
}
public interface ActionMenuChildView {
public boolean needsDividerBefore();
public boolean needsDividerAfter();
}
public static class LayoutParams extends IcsLinearLayout.LayoutParams {
public boolean isOverflowButton;
public int cellsUsed;
public int extraPixels;
public boolean expandable;
public boolean preventEdgeOffset;
public boolean expanded;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(LayoutParams other) {
super((IcsLinearLayout.LayoutParams) other);
isOverflowButton = other.isOverflowButton;
}
public LayoutParams(int width, int height) {
super(width, height);
isOverflowButton = false;
}
public LayoutParams(int width, int height, boolean isOverflowButton) {
super(width, height);
this.isOverflowButton = isOverflowButton;
}
}
}

@ -0,0 +1,222 @@
/*
* 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.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* 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 {
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 View itemView = getItemView(item, convertView, parent);
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 Intended 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;
}
}

@ -1,6 +1,5 @@
/*
* Copyright (C) 2006 The Android Open Source Project
* 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,22 +14,26 @@
* limitations under the License.
*/
package android.support.v4.view;
package com.actionbarsherlock.internal.view.menu;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
import android.support.v4.view.SubMenu;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import android.view.InflateException;
import android.view.View;
import com.actionbarsherlock.internal.view.menu.MenuBuilder;
import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
import com.actionbarsherlock.internal.view.menu.SubMenuBuilder;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* This class is used to instantiate menu XML files into Menu objects.
@ -41,13 +44,70 @@ import com.actionbarsherlock.internal.view.menu.SubMenuBuilder;
* it only works with an XmlPullParser returned from a compiled resource (R.
* <em>something</em> file.)
*/
public final class MenuInflater extends android.view.MenuInflater {
private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] { Context.class };
private static final Class<?>[] PARAM_TYPES = new Class[] { android.support.v4.view.MenuItem.class };
/** Android XML namespace. */
public class MenuInflaterImpl extends android.view.MenuInflater {
private static final String LOG_TAG = "MenuInflater";
private static final String XML_NS = "http://schemas.android.com/apk/res/android";
/*
This doesn't work reliably and I can't yet figure out why.
private static final int[] MenuGroup = new int[] {
//Ascending order by value
android.R.attr.enabled,
android.R.attr.id,
android.R.attr.visible,
android.R.attr.menuCategory,
android.R.attr.orderInCategory,
android.R.attr.checkableBehavior,
};
private static final int MenuGroup_enabled = 0;
private static final int MenuGroup_id = 1;
private static final int MenuGroup_visible = 2;
private static final int MenuGroup_menuCategory = 3;
private static final int MenuGroup_orderInCategory = 4;
private static final int MenuGroup_checkableBehavior = 5;
private static final int[] MenuItem = new int[] {
//Ascending order by value
android.R.attr.icon,
android.R.attr.enabled,
android.R.attr.id,
android.R.attr.checked,
android.R.attr.visible,
android.R.attr.menuCategory,
android.R.attr.orderInCategory,
android.R.attr.title,
android.R.attr.titleCondensed,
android.R.attr.alphabeticShortcut,
android.R.attr.numericShortcut,
android.R.attr.checkable,
android.R.attr.onClick,
android.R.attr.showAsAction,
android.R.attr.actionLayout,
android.R.attr.actionViewClass,
};
private static final int MenuItem_icon = 0;
private static final int MenuItem_enabled = 1;
private static final int MenuItem_id = 2;
private static final int MenuItem_checked = 3;
private static final int MenuItem_visible = 4;
private static final int MenuItem_menuCategory = 5;
private static final int MenuItem_orderInCategory = 6;
private static final int MenuItem_title = 7;
private static final int MenuItem_titleCondensed = 8;
private static final int MenuItem_alphabeticShortcut = 9;
private static final int MenuItem_numericShortcut = 10;
private static final int MenuItem_checkable = 11;
private static final int MenuItem_onClick = 12;
private static final int MenuItem_showAsAction = 13;
private static final int MenuItem_actionLayout = 14;
private static final int MenuItem_actionViewClass = 15;
*/
/** Menu tag name in XML. */
private static final String XML_MENU = "menu";
@ -57,26 +117,29 @@ public final class MenuInflater extends android.view.MenuInflater {
/** Item tag name in XML. */
private static final String XML_ITEM = "item";
private static final int NO_ID = 0;
private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};
private final Object[] mActionViewConstructorArguments;
/** Context from which to inflate resources. */
private final Context mContext;
private Context mContext;
/** Native inflater for context menu fallback. */
private final android.view.MenuInflater mNativeMenuInflater;
/**
* Constructs a menu inflater.
*
* @see Activity#getMenuInflater()
*/
public MenuInflater(Context context, android.view.MenuInflater nativeMenuInflater) {
public MenuInflaterImpl(Context context, android.view.MenuInflater nativeMenuInflater) {
super(context);
mContext = context;
mActionViewConstructorArguments = new Object[] {context};
mNativeMenuInflater = nativeMenuInflater;
}
/**
* Inflate a menu hierarchy from the specified XML resource. Throws
* {@link InflateException} if there is an error.
@ -86,7 +149,6 @@ public final class MenuInflater extends android.view.MenuInflater {
* @param menu The Menu to inflate into. The items and submenus will be
* added to this Menu.
*/
@Override
public void inflate(int menuRes, android.view.Menu menu) {
if (!(menu instanceof MenuBuilder)) {
mNativeMenuInflater.inflate(menuRes, menu);
@ -113,9 +175,9 @@ public final class MenuInflater extends android.view.MenuInflater {
* Called internally to fill the given menu. If a sub menu is seen, it will
* call this recursively.
*/
private void parseMenu(XmlPullParser parser, AttributeSet attrs, MenuBuilder menu)
private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
throws XmlPullParserException, IOException {
ActionBarMenuState menuState = new ActionBarMenuState(menu);
MenuState menuState = new MenuState(menu);
int eventType = parser.getEventType();
String tagName;
@ -152,7 +214,7 @@ public final class MenuInflater extends android.view.MenuInflater {
menuState.readItem(attrs);
} else if (tagName.equals(XML_MENU)) {
// A menu start tag denotes a submenu for an item
SubMenuBuilder subMenu = menuState.addSubMenuItem();
SubMenu subMenu = menuState.addSubMenuItem();
// Parse the submenu into returned SubMenu
parseMenu(parser, attrs, subMenu);
@ -188,7 +250,40 @@ public final class MenuInflater extends android.view.MenuInflater {
}
}
private static class InflatedOnMenuItemClickListener
extends MenuItem.OnMenuItemClickListener {
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
private Context mContext;
private Method mMethod;
public InflatedOnMenuItemClickListener(Context context, String methodName) {
mContext = context;
Class<?> c = context.getClass();
try {
mMethod = c.getMethod(methodName, PARAM_TYPES);
} catch (Exception e) {
InflateException ex = new InflateException(
"Couldn't resolve menu item onClick handler " + methodName +
" in class " + c.getName());
ex.initCause(e);
throw ex;
}
}
public boolean onMenuItemClick(MenuItem item) {
try {
if (mMethod.getReturnType() == Boolean.TYPE) {
return (Boolean) mMethod.invoke(mContext, item);
} else {
mMethod.invoke(mContext, item);
return true;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* State for the current menu.
@ -196,8 +291,8 @@ public final class MenuInflater extends android.view.MenuInflater {
* Groups can not be nested unless there is another menu (which will have
* its state class).
*/
private final class ActionBarMenuState {
private final MenuBuilder menu;
private class MenuState {
private Menu menu;
/*
* Group state is set on items as they are added, allowing an item to
@ -213,8 +308,8 @@ public final class MenuInflater extends android.view.MenuInflater {
private boolean itemAdded;
private int itemId;
private int itemCategoryOrder;
private String itemTitle;
private String itemTitleCondensed;
private CharSequence itemTitle;
private CharSequence itemTitleCondensed;
private int itemIconResId;
private char itemAlphabeticShortcut;
private char itemNumericShortcut;
@ -228,28 +323,31 @@ public final class MenuInflater extends android.view.MenuInflater {
private boolean itemChecked;
private boolean itemVisible;
private boolean itemEnabled;
private String itemListenerMethodName;
/**
* Sync to attrs.xml enum, values in MenuItem:
* - 0: never
* - 1: ifRoom
* - 2: always
* - -1: Safe sentinel for "no value".
*/
private int itemShowAsAction;
private int itemActionLayout;
private int itemActionViewLayout;
private String itemActionViewClassName;
private static final int defaultGroupId = View.NO_ID;
private static final int defaultItemId = View.NO_ID;
private String itemListenerMethodName;
private static final int defaultGroupId = NO_ID;
private static final int defaultItemId = NO_ID;
private static final int defaultItemCategory = 0;
private static final int defaultItemOrder = 0;
private static final int defaultItemCheckable = 0;
private static final boolean defaultItemChecked = false;
private static final boolean defaultItemVisible = true;
private static final boolean defaultItemEnabled = true;
private static final int defaultItemShowAsAction = 0;
private static final int defaultIconResId = View.NO_ID;
/** Mirror of package-scoped Menu.CATEGORY_MASK. */
private static final int Menu__CATEGORY_MASK = 0xffff0000;
/** Mirror of package-scoped Menu.USER_MASK. */
private static final int Menu__USER_MASK = 0x0000ffff;
public ActionBarMenuState(MenuBuilder menu) {
public MenuState(final Menu menu) {
this.menu = menu;
resetGroup();
@ -268,24 +366,20 @@ public final class MenuInflater extends android.view.MenuInflater {
* Called when the parser is pointing to a group tag.
*/
public void readGroup(AttributeSet attrs) {
//TypedArray a = mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuGroup);
//TypedArray a = mContext.obtainStyledAttributes(attrs,
// /*com.android.internal.R.styleable.*/MenuGroup);
//groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
//groupId = a.getResourceId(/*com.android.internal.R.styleable.*/MenuGroup_id, defaultGroupId);
groupId = attrs.getAttributeResourceValue(XML_NS, "id", defaultGroupId);
//groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
//groupCategory = a.getInt(/*com.android.internal.R.styleable.*/MenuGroup_menuCategory, defaultItemCategory);
groupCategory = attrs.getAttributeIntValue(XML_NS, "menuCategory", defaultItemCategory);
//groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
//groupOrder = a.getInt(/*com.android.internal.R.styleable.*/MenuGroup_orderInCategory, defaultItemOrder);
groupOrder = attrs.getAttributeIntValue(XML_NS, "orderInCategory", defaultItemOrder);
//groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
//groupCheckable = a.getInt(/*com.android.internal.R.styleable.*/MenuGroup_checkableBehavior, defaultItemCheckable);
groupCheckable = attrs.getAttributeIntValue(XML_NS, "checkableBehavior", defaultItemCheckable);
//groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
//groupVisible = a.getBoolean(/*com.android.internal.R.styleable.*/MenuGroup_visible, defaultItemVisible);
groupVisible = attrs.getAttributeBooleanValue(XML_NS, "visible", defaultItemVisible);
//groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
//groupEnabled = a.getBoolean(/*com.android.internal.R.styleable.*/MenuGroup_enabled, defaultItemEnabled);
groupEnabled = attrs.getAttributeBooleanValue(XML_NS, "enabled", defaultItemEnabled);
//a.recycle();
@ -295,71 +389,64 @@ public final class MenuInflater extends android.view.MenuInflater {
* Called when the parser is pointing to an item tag.
*/
public void readItem(AttributeSet attrs) {
//TypedArray a = mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuItem);
//TypedArray a = mContext.obtainStyledAttributes(attrs,
// /*com.android.internal.R.styleable.*/MenuItem);
// Inherit attributes from the group as default value
//itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
//itemId = a.getResourceId(/*com.android.internal.R.styleable.*/MenuItem_id, defaultItemId);
itemId = attrs.getAttributeResourceValue(XML_NS, "id", defaultItemId);
//final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);
//final int category = a.getInt(/*com.android.internal.R.styleable.*/MenuItem_menuCategory, groupCategory);
final int category = attrs.getAttributeIntValue(XML_NS, "menuCategory", groupCategory);
//final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);
//final int order = a.getInt(/*com.android.internal.R.styleable.*/MenuItem_orderInCategory, groupOrder);
final int order = attrs.getAttributeIntValue(XML_NS, "orderInCategory", groupOrder);
//itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
itemCategoryOrder = (category & Menu__CATEGORY_MASK) | (order & Menu__USER_MASK);
//itemTitle = a.getString(com.android.internal.R.styleable.MenuItem_title);
itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
//itemTitle = a.getText(/*com.android.internal.R.styleable.*/MenuItem_title);
final int itemTitleId = attrs.getAttributeResourceValue(XML_NS, "title", 0);
if (itemTitleId != 0) {
itemTitle = mContext.getString(itemTitleId);
} else {
itemTitle = attrs.getAttributeValue(XML_NS, "title");
}
//itemTitleCondensed = a.getString(com.android.internal.R.styleable.MenuItem_titleCondensed);
//itemTitleCondensed = a.getText(/*com.android.internal.R.styleable.*/MenuItem_titleCondensed);
final int itemTitleCondensedId = attrs.getAttributeResourceValue(XML_NS, "titleCondensed", 0);
if (itemTitleCondensedId != 0) {
itemTitleCondensed = mContext.getString(itemTitleCondensedId);
} else {
itemTitleCondensed = attrs.getAttributeValue(XML_NS, "titleCondensed");
}
//itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
itemIconResId = attrs.getAttributeResourceValue(XML_NS, "icon", defaultIconResId);
//itemAlphabeticShortcut = getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
itemAlphabeticShortcut = getShortcut(attrs.getAttributeValue(XML_NS, "alphabeticShortcut"));
//itemNumericShortcut = getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));
itemNumericShortcut = getShortcut(attrs.getAttributeValue(XML_NS, "numericShortcut"));
//if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
//itemIconResId = a.getResourceId(/*com.android.internal.R.styleable.*/MenuItem_icon, 0);
itemIconResId = attrs.getAttributeResourceValue(XML_NS, "icon", 0);
//itemAlphabeticShortcut =
// getShortcut(a.getString(/*com.android.internal.R.styleable.*/MenuItem_alphabeticShortcut));
itemAlphabeticShortcut =
getShortcut(attrs.getAttributeValue(XML_NS, "alphabeticShortcut"));
//itemNumericShortcut =
// getShortcut(a.getString(/*com.android.internal.R.styleable.*/MenuItem_numericShortcut));
itemNumericShortcut =
getShortcut(attrs.getAttributeValue(XML_NS, "numericShortcut"));
//if (a.hasValue(/*com.android.internal.R.styleable.*/MenuItem_checkable)) {
if (attrs.getAttributeValue(XML_NS, "checkable") != null) {
// Item has attribute checkable, use it
//itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
//itemCheckable = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_checkable, false) ? 1 : 0;
itemCheckable = attrs.getAttributeBooleanValue(XML_NS, "checkable", false) ? 1 : 0;
} else {
// Item does not have attribute, use the group's (group can have one more state
// for checkable that represents the exclusive checkable)
itemCheckable = groupCheckable;
}
//itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
//itemChecked = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_checked, defaultItemChecked);
itemChecked = attrs.getAttributeBooleanValue(XML_NS, "checked", defaultItemChecked);
//itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
//itemVisible = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_visible, groupVisible);
itemVisible = attrs.getAttributeBooleanValue(XML_NS, "visible", groupVisible);
//itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
//itemEnabled = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_enabled, groupEnabled);
itemEnabled = attrs.getAttributeBooleanValue(XML_NS, "enabled", groupEnabled);
//presumed emulation of 3.0+'s MenuInflator:
//itemShowAsAction = a.getInt(/*com.android.internal.R.styleable.*/MenuItem_showAsAction, -1);
itemShowAsAction = attrs.getAttributeIntValue(XML_NS, "showAsAction", -1);
//itemListenerMethodName = a.getString(/*com.android.internal.R.styleable.*/MenuItem_onClick);
itemListenerMethodName = attrs.getAttributeValue(XML_NS, "onClick");
itemShowAsAction = attrs.getAttributeIntValue(XML_NS, "showAsAction", defaultItemShowAsAction);
itemActionLayout = attrs.getAttributeResourceValue(XML_NS, "actionLayout", 0);
//itemActionViewLayout = a.getResourceId(/*com.android.internal.R.styleable.*/MenuItem_actionLayout, 0);
itemActionViewLayout = attrs.getAttributeResourceValue(XML_NS, "actionLayout", 0);
//itemActionViewClassName = a.getString(/*com.android.internal.R.styleable.*/MenuItem_actionViewClass);
itemActionViewClassName = attrs.getAttributeValue(XML_NS, "actionViewClass");
//a.recycle();
@ -375,7 +462,7 @@ public final class MenuInflater extends android.view.MenuInflater {
}
}
private void setItem(MenuItemImpl item) {
private void setItem(MenuItem item) {
item.setChecked(itemChecked)
.setVisible(itemVisible)
.setEnabled(itemEnabled)
@ -385,42 +472,52 @@ public final class MenuInflater extends android.view.MenuInflater {
.setAlphabeticShortcut(itemAlphabeticShortcut)
.setNumericShortcut(itemNumericShortcut);
if (itemShowAsAction > 0) {
if (itemShowAsAction >= 0) {
item.setShowAsAction(itemShowAsAction);
}
if (itemListenerMethodName != null) {
if (MenuInflater.this.mContext.isRestricted()) {
throw new IllegalStateException("The android:onClick attribute cannot be used within a restricted context");
if (mContext.isRestricted()) {
throw new IllegalStateException("The android:onClick attribute cannot "
+ "be used within a restricted context");
}
item.setOnMenuItemClickListener(new InflatedOnMenuItemClickListener(itemListenerMethodName));
item.setOnMenuItemClickListener(
new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName));
}
if (itemCheckable >= 2) {
item.setExclusiveCheckable(true);
if (item instanceof MenuItemImpl) {
MenuItemImpl impl = (MenuItemImpl) item;
if (itemCheckable >= 2) {
impl.setExclusiveCheckable(true);
}
}
boolean actionViewSpecified = false;
if (itemActionViewClassName != null) {
try {
Context context = MenuInflater.this.mContext;
ClassLoader loader = context.getClassLoader();
Class<?> actionViewClass = Class.forName(itemActionViewClassName, true, loader);
Constructor<?> constructor = actionViewClass.getConstructor(ACTION_VIEW_CONSTRUCTOR_SIGNATURE);
View actionView = (View)constructor.newInstance(new Object[] { context });
item.setActionView(actionView);
} catch (Exception e) {
throw new InflateException(e);
View actionView = (View) newInstance(itemActionViewClassName,
ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments);
item.setActionView(actionView);
actionViewSpecified = true;
}
if (itemActionViewLayout > 0) {
if (!actionViewSpecified) {
item.setActionView(itemActionViewLayout);
actionViewSpecified = true;
} else {
Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'."
+ " Action view already specified.");
}
} else if (itemActionLayout > 0) {
item.setActionView(itemActionLayout);
}
}
public void addItem() {
itemAdded = true;
setItem((MenuItemImpl)menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
}
public SubMenuBuilder addSubMenuItem() {
public SubMenu addSubMenuItem() {
itemAdded = true;
SubMenuBuilder subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
setItem(subMenu.getItem());
return subMenu;
}
@ -428,36 +525,18 @@ public final class MenuInflater extends android.view.MenuInflater {
public boolean hasAddedItem() {
return itemAdded;
}
}
class InflatedOnMenuItemClickListener extends android.support.v4.view.MenuItem.OnMenuItemClickListener {
private Method mMethod;
public InflatedOnMenuItemClickListener(String methodName) {
final Class<?> localClass = mContext.getClass();
@SuppressWarnings("unchecked")
private <T> T newInstance(String className, Class<?>[] constructorSignature,
Object[] arguments) {
try {
mMethod = localClass.getMethod(methodName, PARAM_TYPES);
Class<?> clazz = mContext.getClassLoader().loadClass(className);
Constructor<?> constructor = clazz.getConstructor(constructorSignature);
return (T) constructor.newInstance(arguments);
} catch (Exception e) {
StringBuilder b = new StringBuilder();
b.append("Couldn't resolve menu item onClick handler ");
b.append(methodName);
b.append(" in class ");
b.append(localClass.getName());
throw new InflateException(b.toString(), e);
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
final Object[] params = new Object[] { item };
try {
if (mMethod.getReturnType() == Boolean.TYPE) {
return (Boolean)mMethod.invoke(mContext, params);
}
return false;
} catch (Exception e) {
throw new RuntimeException(e);
Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);
}
return null;
}
}
}
}

@ -1,6 +1,5 @@
/*
* Copyright (C) 2006 The Android Open Source Project
* 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,128 +16,131 @@
package com.actionbarsherlock.internal.view.menu;
import java.lang.ref.WeakReference;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.view.MenuItem;
import android.support.v4.view.SubMenu;
import android.util.Log;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewDebug;
import android.widget.LinearLayout;
/**
* An implementation of the {@link android.view.MenuItem} interface for use in
* inflating menu XML resources to be added to a third-party action bar.
*
* @author Jake Wharton <jakewharton@gmail.com>
* @see <a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/com/android/internal/view/menu/MenuItemImpl.java">com.android.internal.view.menu.MenuItemImpl</a>
* @hide
*/
public final class MenuItemImpl implements MenuItem {
private static final String TAG = "MenuItemImpl";
private final MenuBuilder mMenu;
private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
SHOW_AS_ACTION_IF_ROOM |
SHOW_AS_ACTION_ALWAYS;
private final int mItemId;
private final int mGroupId;
private final int mId;
private final int mGroup;
private final int mCategoryOrder;
private final int mOrdering;
private Intent mIntent;
private CharSequence mTitle;
private CharSequence mTitleCondensed;
private char mNumericalShortcut;
private char mAlphabeticalShortcut;
private int mShowAsAction;
private Intent mIntent;
private char mShortcutNumericChar;
private char mShortcutAlphabeticChar;
/** The icon's drawable which is only created as needed */
private Drawable mIconDrawable;
/**
* The icon's resource ID which is used to get the Drawable when it is
* needed (if the Drawable isn't already obtained--only one of the two is
* needed).
*/
private int mIconResId = NO_ICON;
/** The menu to which this item belongs */
private MenuBuilder mMenu;
/** If this item should launch a sub menu, this is the sub menu to launch */
private SubMenuBuilder mSubMenu;
private Runnable mItemCallback;
private OnMenuItemClickListener mClickListener;
private Drawable mIcon;
private int mIconRes = View.NO_ID;
private MenuItem.OnMenuItemClickListener mClickListener;
private int mFlags = ENABLED;
private static final int CHECKABLE = 0x00000001;
private static final int CHECKED = 0x00000002;
private static final int EXCLUSIVE = 0x00000004;
private static final int HIDDEN = 0x00000008;
private static final int ENABLED = 0x00000010;
private static final int IS_ACTION = 0x00000020;
private int mShowAsAction = SHOW_AS_ACTION_NEVER;
private View mActionView;
private int mActionViewRes = View.NO_ID;
int mFlags = ENABLED;
static final int CHECKABLE = 0x01;
static final int CHECKED = 0x02;
static final int EXCLUSIVE = 0x04;
static final int HIDDEN = 0x08;
static final int ENABLED = 0x10;
static final int IS_ACTION = 0x20;
private final WeakReference<MenuView.ItemView>[] mItemViews;
private final DialogInterface.OnClickListener subMenuClick = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
dialog.dismiss();
mSubMenu.getItem(index).invoke();
}
};
private final DialogInterface.OnMultiChoiceClickListener subMenuMultiClick = new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int index, boolean isChecked) {
dialog.dismiss();
mSubMenu.getItem(index).setChecked(isChecked);
}
};
/** Used for the icon resource ID if this item does not have an icon */
static final int NO_ICON = 0;
/**
* Current use case is for context menu: Extra information linked to the
* View that added this item to the context menu.
*/
private ContextMenuInfo mMenuInfo;
private static String sPrependShortcutLabel;
private static String sEnterShortcutLabel;
private static String sDeleteShortcutLabel;
private static String sSpaceShortcutLabel;
/**
* Create a new action bar menu item.
* Instantiates this menu item.
*
* @param context Context used if resource resolution is required.
* @param itemId A unique ID. Used in the activity callback.
* @param groupId Group ID. Currently unused.
* @param order Item order. Currently unused.
* @param title Title of the item.
* @param menu
* @param group Item ordering grouping control. The item will be added after
* all other items whose order is <= this number, and before any
* that are larger than it. This can also be used to define
* groups of items for batch state changes. Normally use 0.
* @param id Unique item ID. Use 0 if you do not need a unique ID.
* @param categoryOrder The ordering for this item.
* @param title The text to display for the item.
*/
@SuppressWarnings("unchecked")
public MenuItemImpl(MenuBuilder menu, int groupId, int itemId, int order, int ordering, CharSequence title, int showAsAction) {
mMenu = menu;
MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
CharSequence title, int showAsAction) {
/* TODO if (sPrependShortcutLabel == null) {
// This is instantiated from the UI thread, so no chance of sync issues
sPrependShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.prepend_shortcut_label);
sEnterShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.menu_enter_shortcut_label);
sDeleteShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.menu_delete_shortcut_label);
sSpaceShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.menu_space_shortcut_label);
}*/
mItemId = itemId;
mGroupId = groupId;
mCategoryOrder = order;
mMenu = menu;
mId = id;
mGroup = group;
mCategoryOrder = categoryOrder;
mOrdering = ordering;
mTitle = title;
mShowAsAction = showAsAction;
mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
}
/**
* Invokes the item by calling various listeners or callbacks.
*
* @return true if the invocation was handled, false otherwise
*/
public boolean invoke() {
if (hasSubMenu()) {
AlertDialog.Builder builder = new AlertDialog.Builder(mMenu.getContext());
builder.setTitle(getTitle());
final boolean isExclusive = mSubMenu.getItem(0).isExclusiveCheckable();
final boolean isCheckable = mSubMenu.getItem(0).isCheckable();
final CharSequence[] titles = getSubMenuTitles();
if (isExclusive) {
builder.setSingleChoiceItems(titles, getSubMenuSelected(), subMenuClick);
} else if (isCheckable) {
builder.setMultiChoiceItems(titles, getSubMenuChecked(), subMenuMultiClick);
} else {
builder.setItems(titles, subMenuClick);
}
builder.show();
return true;
}
if (mClickListener != null &&
mClickListener.onMenuItemClick(this)) {
return true;
}
MenuBuilder.Callback callback = mMenu.getRootMenu().getCallback();
if (callback != null &&
callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
return true;
}
@ -159,505 +161,416 @@ public final class MenuItemImpl implements MenuItem {
return false;
}
private CharSequence[] getSubMenuTitles() {
final int count = mSubMenu.size();
CharSequence[] list = new CharSequence[count];
for (int i = 0; i < count; i++) {
list[i] = mSubMenu.getItem(i).getTitle();
}
return list;
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
private int getSubMenuSelected() {
final int count = mSubMenu.size();
for (int i = 0; i < count; i++) {
if (mSubMenu.getItem(i).isChecked()) {
return i;
}
public MenuItem setEnabled(boolean enabled) {
if (enabled) {
mFlags |= ENABLED;
} else {
mFlags &= ~ENABLED;
}
return -1;
}
private boolean[] getSubMenuChecked() {
final int count = mSubMenu.size();
boolean[] checked = new boolean[count];
for (int i = 0; i < count; i++) {
checked[i] = mSubMenu.getItem(i).isChecked();
}
return checked;
}
mMenu.onItemsChanged(false);
private boolean hasItemView(int menuType) {
return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
return this;
}
public void setItemView(int type, MenuView.ItemView itemView) {
mItemViews[type] = new WeakReference<MenuView.ItemView>(itemView);
public int getGroupId() {
return mGroup;
}
public void addTo(android.view.Menu menu) {
if (hasSubMenu()) {
android.view.SubMenu subMenu = menu.addSubMenu(mGroupId, mItemId, mCategoryOrder, mTitle);
if (mIconRes != View.NO_ID) {
subMenu.setIcon(mIconRes);
} else {
subMenu.setIcon(mIcon);
}
for (MenuItemImpl item : mSubMenu.getItems()) {
item.addTo(subMenu);
}
if (mSubMenu.getItem(0).isExclusiveCheckable()) {
int checked = getSubMenuSelected();
if (checked != -1) {
subMenu.getItem(checked).setChecked(true);
}
}
} else {
android.view.MenuItem item = menu.add(mGroupId, mItemId, mCategoryOrder, mTitle)
.setAlphabeticShortcut(mAlphabeticalShortcut)
.setNumericShortcut(mNumericalShortcut)
.setVisible(isVisible())
.setIntent(mIntent)
.setCheckable(isCheckable())
.setChecked(isChecked())
.setOnMenuItemClickListener(mClickListener);
if (isExclusiveCheckable()) {
menu.setGroupCheckable(mGroupId, true, true);
}
//Create and initialize a native itemview wrapper
NativeMenuItemView nativeWrapper = new NativeMenuItemView(item);
nativeWrapper.initialize(this, MenuBuilder.TYPE_NATIVE);
//Associate the itemview to this so changes will be reflected
setItemView(MenuBuilder.TYPE_NATIVE, nativeWrapper);
}
@ViewDebug.CapturedViewProperty
public int getItemId() {
return mId;
}
/**
* Get whether or not this item is being shown on the action bar.
*
* @return {@code true} if shown, {@code false} otherwise.
*/
public boolean isShownOnActionBar() {
return (mFlags & IS_ACTION) == IS_ACTION;
public int getOrder() {
return mCategoryOrder;
}
/**
* Denote whether or not this menu item is being shown on the action bar.
*
* @param isShownOnActionBar {@code true} if shown or {@code false}.
*/
public void setIsShownOnActionBar(boolean isShownOnActionBar) {
mFlags = (mFlags & ~IS_ACTION) | (isShownOnActionBar ? IS_ACTION : 0);
public int getOrdering() {
return mOrdering;
}
@Override
public Intent getIntent() {
return this.mIntent;
return mIntent;
}
@Override
public int getItemId() {
return this.mItemId;
public MenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
}
@Override
public CharSequence getTitle() {
return this.mTitle;
Runnable getCallback() {
return mItemCallback;
}
@Override
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
public MenuItem setCallback(Runnable callback) {
mItemCallback = callback;
return this;
}
@Override
public boolean isVisible() {
return (mFlags & HIDDEN) == 0;
public char getAlphabeticShortcut() {
return mShortcutAlphabeticChar;
}
@Override
public MenuItem setEnabled(boolean enabled) {
final boolean oldValue = isEnabled();
mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
if (oldValue != enabled) {
for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
if (hasItemView(i)) {
mItemViews[i].get().setEnabled(enabled);
}
}
}
return this;
}
public MenuItem setAlphabeticShortcut(char alphaChar) {
if (mShortcutAlphabeticChar == alphaChar) return this;
@Override
public MenuItem setIcon(int iconResourceId) {
mIcon = null;
mIconRes = iconResourceId;
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
if (mIconRes != View.NO_ID) {
setIconOnViews(mMenu.getContext().getResources().getDrawable(mIconRes));
}
mMenu.onItemsChanged(false);
return this;
}
@Override
public MenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
public char getNumericShortcut() {
return mShortcutNumericChar;
}
@Override
public MenuItem setTitle(CharSequence title) {
mTitle = title;
return this;
}
public MenuItem setNumericShortcut(char numericChar) {
if (mShortcutNumericChar == numericChar) return this;
@Override
public MenuItem setTitle(int titleResourceId) {
mTitle = mMenu.getContext().getResources().getString(titleResourceId);
return this;
}
mShortcutNumericChar = numericChar;
mMenu.onItemsChanged(false);
@Override
public MenuItem setVisible(boolean visible) {
final boolean oldValue = isVisible();
mFlags = (mFlags & ~HIDDEN) | (visible ? 0 : HIDDEN);
if (oldValue != visible) {
for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
if (hasItemView(i)) {
mItemViews[i].get().setVisible(visible);
}
}
}
return this;
}
@Override
public boolean isChecked() {
return (mFlags & CHECKED) == CHECKED;
}
public MenuItem setShortcut(char numericChar, char alphaChar) {
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
@Override
public MenuItem setChecked(boolean checked) {
if ((mFlags & EXCLUSIVE) == EXCLUSIVE) {
// Call the method on the Menu since it knows about the others in this
// exclusive checkable group
mMenu.setExclusiveItemChecked(this);
} else {
setCheckedInt(checked);
}
mMenu.onItemsChanged(false);
return this;
}
void setCheckedInt(boolean checked) {
final boolean oldValue = isChecked();
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
if (oldValue != checked) {
for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
if (hasItemView(i)) {
mItemViews[i].get().setChecked(checked);
}
}
}
/**
* @return The active shortcut (based on QWERTY-mode of the menu).
*/
char getShortcut() {
return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
}
@Override
public boolean isCheckable() {
return (mFlags & CHECKABLE) == CHECKABLE;
}
/**
* @return The label to show for the shortcut. This includes the chording
* key (for example 'Menu+a'). Also, any non-human readable
* characters should be human readable (for example 'Menu+enter').
*/
String getShortcutLabel() {
@Override
public MenuItem setCheckable(boolean checkable) {
final boolean oldValue = isCheckable();
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
if (oldValue != checkable) {
for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
if (hasItemView(i)) {
mItemViews[i].get().setCheckable(checkable);
}
}
char shortcut = getShortcut();
if (shortcut == 0) {
return "";
}
return this;
}
StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
switch (shortcut) {
public void setExclusiveCheckable(boolean exclusive) {
mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
}
case '\n':
sb.append(sEnterShortcutLabel);
break;
public boolean isExclusiveCheckable() {
return (mFlags & EXCLUSIVE) == EXCLUSIVE;
}
case '\b':
sb.append(sDeleteShortcutLabel);
break;
@Override
public CharSequence getTitleCondensed() {
return mTitleCondensed;
case ' ':
sb.append(sSpaceShortcutLabel);
break;
default:
sb.append(shortcut);
break;
}
return sb.toString();
}
@Override
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
return this;
/**
* @return Whether this menu item should be showing shortcuts (depends on
* whether the menu should show shortcuts and whether this item has
* a shortcut defined)
*/
boolean shouldShowShortcut() {
// Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
return mMenu.isShortcutsVisible() && (getShortcut() != 0);
}
@Override
public int getGroupId() {
return mGroupId;
public SubMenu getSubMenu() {
return mSubMenu;
}
@Override
public int getOrder() {
return mCategoryOrder;
public boolean hasSubMenu() {
return mSubMenu != null;
}
public int getOrdering() {
return mOrdering;
void setSubMenu(SubMenuBuilder subMenu) {
mSubMenu = subMenu;
subMenu.setHeaderTitle(getTitle());
}
@Override
public SubMenuBuilder getSubMenu() {
return mSubMenu;
@ViewDebug.CapturedViewProperty
public CharSequence getTitle() {
return mTitle;
}
/**
* Set the sub-menu of this item.
* Gets the title for a particular {@link ItemView}
*
* @param subMenu Sub-menu instance.
* @return This Item so additional setters can be called.
* @param itemView The ItemView that is receiving the title
* @return Either the title or condensed title based on what the ItemView
* prefers
*/
MenuItem setSubMenu(SubMenuBuilder subMenu) {
mSubMenu = subMenu;
return this;
CharSequence getTitleForItemView(MenuView.ItemView itemView) {
return ((itemView != null) && itemView.prefersCondensedTitle())
? getTitleCondensed()
: getTitle();
}
@Override
public boolean hasSubMenu() {
return (mSubMenu != null) && (mSubMenu.size() > 0);
}
public MenuItem setTitle(CharSequence title) {
mTitle = title;
@Override
public char getAlphabeticShortcut() {
return mAlphabeticalShortcut;
}
mMenu.onItemsChanged(false);
@Override
public char getNumericShortcut() {
return mNumericalShortcut;
}
if (mSubMenu != null) {
mSubMenu.setHeaderTitle(title);
}
@Override
public MenuItem setAlphabeticShortcut(char alphaChar) {
mAlphabeticalShortcut = Character.toLowerCase(alphaChar);
return this;
}
@Override
public MenuItem setNumericShortcut(char numericChar) {
mNumericalShortcut = numericChar;
return this;
public MenuItem setTitle(int title) {
return setTitle(mMenu.getContext().getString(title));
}
@Override
public MenuItem setShortcut(char numericChar, char alphaChar) {
setNumericShortcut(numericChar);
setAlphabeticShortcut(alphaChar);
return this;
public CharSequence getTitleCondensed() {
return mTitleCondensed != null ? mTitleCondensed : mTitle;
}
@Override
public void setShowAsAction(int actionEnum) {
mShowAsAction = actionEnum;
}
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
public int getShowAsAction() {
return mShowAsAction;
}
// Could use getTitle() in the loop below, but just cache what it would do here
if (title == null) {
title = mTitle;
}
public boolean showsActionItemText() {
return mMenu.getShowsActionItemText();
}
mMenu.onItemsChanged(false);
@Override
public View getActionView() {
if (mActionView != null) {
return mActionView;
}
if (mActionViewRes != View.NO_ID) {
return LayoutInflater.from(mMenu.getContext()).inflate(mActionViewRes, null, false);
}
return null;
return this;
}
@Override
public Drawable getIcon() {
if (mIcon != null) {
return mIcon;
if (mIconDrawable != null) {
return mIconDrawable;
}
if (mIconRes != View.NO_ID) {
return mMenu.getContext().getResources().getDrawable(mIconRes);
if (mIconResId != NO_ICON) {
return mMenu.getResources().getDrawable(mIconResId);
}
return null;
}
@Override
public ContextMenuInfo getMenuInfo() {
return null;
public MenuItem setIcon(Drawable icon) {
mIconResId = NO_ICON;
mIconDrawable = icon;
mMenu.onItemsChanged(false);
return this;
}
@Override
public MenuItem setActionView(View view) {
mActionView = view;
mActionViewRes = View.NO_ID;
setActionViewOnViews(mActionView);
public MenuItem setIcon(int iconResId) {
mIconDrawable = null;
mIconResId = iconResId;
// If we have a view, we need to push the Drawable to them
mMenu.onItemsChanged(false);
return this;
}
@Override
public MenuItem setActionView(int resId) {
mActionView = null;
mActionViewRes = resId;
public boolean isCheckable() {
return (mFlags & CHECKABLE) == CHECKABLE;
}
if (mActionViewRes != View.NO_ID) {
setActionViewOnViews(LayoutInflater.from(mMenu.getContext()).inflate(mActionViewRes, null, false));
public MenuItem setCheckable(boolean checkable) {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
if (oldFlags != mFlags) {
mMenu.onItemsChanged(false);
}
return this;
}
void setActionViewOnViews(View view) {
for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
if (hasItemView(i)) {
mItemViews[i].get().setActionView(view);
}
}
public void setExclusiveCheckable(boolean exclusive) {
mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
}
@Override
public MenuItem setIcon(Drawable icon) {
mIcon = icon;
mIconRes = View.NO_ID;
setIconOnViews(icon);
return this;
public boolean isExclusiveCheckable() {
return (mFlags & EXCLUSIVE) != 0;
}
void setIconOnViews(Drawable icon) {
for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
if (hasItemView(i)) {
mItemViews[i].get().setIcon(icon);
}
public boolean isChecked() {
return (mFlags & CHECKED) == CHECKED;
}
public MenuItem setChecked(boolean checked) {
if ((mFlags & EXCLUSIVE) != 0) {
// Call the method on the Menu since it knows about the others in this
// exclusive checkable group
mMenu.setExclusiveItemChecked(this);
} else {
setCheckedInt(checked);
}
return this;
}
@Override
public android.view.MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener menuItemClickListener) {
return this.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return menuItemClickListener.onMenuItemClick(new MenuItemWrapper(item));
}
});
void setCheckedInt(boolean checked) {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
if (oldFlags != mFlags) {
mMenu.onItemsChanged(false);
}
}
@Override
public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
mClickListener = menuItemClickListener;
return this;
public boolean isVisible() {
return (mFlags & HIDDEN) == 0;
}
/**
* Returns the currently set menu click listener for this item.
* Changes the visibility of the item. This method DOES NOT notify the
* parent menu of a change in this item, so this should only be called from
* methods that will eventually trigger this change. If unsure, use {@link #setVisible(boolean)}
* instead.
*
* @return Click listener or {@code null}.
* @param shown Whether to show (true) or hide (false).
* @return Whether the item's shown state was changed
*/
public OnMenuItemClickListener getOnMenuItemClickListener() {
return mClickListener;
boolean setVisibleInt(boolean shown) {
final int oldFlags = mFlags;
mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
return oldFlags != mFlags;
}
public MenuItem setVisible(boolean shown) {
// Try to set the shown state to the given state. If the shown state was changed
// (i.e. the previous state isn't the same as given state), notify the parent menu that
// the shown state has changed for this item
if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
return this;
}
public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
mClickListener = clickListener;
return this;
}
public static final class NativeMenuItemView implements MenuView.ItemView {
private final android.view.MenuItem mItem;
public MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener clickListener) {
mClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return clickListener.onMenuItemClick(item);
}
};
return this;
}
public NativeMenuItemView(android.view.MenuItem item) {
mItem = item;
}
@Override
public String toString() {
return mTitle.toString();
}
void setMenuInfo(ContextMenuInfo menuInfo) {
mMenuInfo = menuInfo;
}
@Override
public MenuItemImpl getItemData() {
return null;
}
public ContextMenuInfo getMenuInfo() {
return mMenuInfo;
}
@Override
public void initialize(MenuItemImpl itemData, int menuType) {
setIcon(itemData.getIcon());
setTitle(itemData.getTitle());
setEnabled(itemData.isEnabled());
setCheckable(itemData.isCheckable());
setChecked(itemData.isChecked());
setActionView(itemData.getActionView());
setVisible(itemData.isVisible());
}
public void actionFormatChanged() {
mMenu.onItemActionRequestChanged(this);
}
@Override
public boolean prefersCondensedTitle() {
return true;
}
/**
* @return Whether the menu should show icons for menu items.
*/
public boolean shouldShowIcon() {
return mMenu.getOptionalIconsVisible();
}
@Override
public void setCheckable(boolean checkable) {
mItem.setCheckable(checkable);
}
public boolean isActionButton() {
return (mFlags & IS_ACTION) == IS_ACTION;
}
@Override
public void setChecked(boolean checked) {
mItem.setChecked(checked);
}
public boolean requestsActionButton() {
return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
}
@Override
public void setEnabled(boolean enabled) {
mItem.setEnabled(enabled);
}
public boolean requiresActionButton() {
return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
}
@Override
public void setIcon(Drawable icon) {
mItem.setIcon(icon);
public void setIsActionButton(boolean isActionButton) {
if (isActionButton) {
mFlags |= IS_ACTION;
} else {
mFlags &= ~IS_ACTION;
}
}
@Override
public void setShortcut(boolean showShortcut, char shortcutKey) {
//Not supported
}
public boolean showsTextAsAction() {
return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
}
@Override
public void setTitle(CharSequence title) {
mItem.setTitle(title);
public void setShowAsAction(int actionEnum) {
switch (actionEnum & SHOW_AS_ACTION_MASK) {
case SHOW_AS_ACTION_ALWAYS:
case SHOW_AS_ACTION_IF_ROOM:
case SHOW_AS_ACTION_NEVER:
// Looks good!
break;
default:
// Mutually exclusive options selected!
throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
+ " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
}
mShowAsAction = actionEnum;
mMenu.onItemActionRequestChanged(this);
}
@Override
public boolean showsIcon() {
return true;
public MenuItem setActionView(View view) {
mActionView = view;
if (view != null && view.getId() == View.NO_ID && mId > 0) {
view.setId(mId);
}
mMenu.onItemActionRequestChanged(this);
return this;
}
@Override
public void setActionView(View actionView) {
//Not supported
}
public MenuItem setActionView(int resId) {
final Context context = mMenu.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
setActionView(inflater.inflate(resId, new LinearLayout(context), false));
return this;
}
@Override
public void setVisible(boolean visible) {
mItem.setVisible(visible);
public View getActionView() {
if (mActionView != null) {
return mActionView;
} else {
return null;
}
}
}
}

@ -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);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save