diff options
| -rw-r--r-- | api/current.xml | 34 | ||||
| -rw-r--r-- | core/java/android/app/Activity.java | 4 | ||||
| -rw-r--r-- | core/java/android/app/BackStackEntry.java | 37 | ||||
| -rw-r--r-- | core/java/android/app/FragmentManager.java | 63 | ||||
| -rw-r--r-- | core/java/android/app/ListFragment.java | 108 | ||||
| -rw-r--r-- | core/java/android/app/LoaderManagingFragment.java | 2 | ||||
| -rw-r--r-- | core/res/res/layout/list_content_rich.xml | 56 | ||||
| -rw-r--r-- | core/res/res/values/strings.xml | 3 |
8 files changed, 288 insertions, 19 deletions
diff --git a/api/current.xml b/api/current.xml index 50546d3..dc4ba2c 100644 --- a/api/current.xml +++ b/api/current.xml @@ -28030,6 +28030,19 @@ <parameter name="id" type="long"> </parameter> </method> +<method name="setEmptyText" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="text" type="java.lang.CharSequence"> +</parameter> +</method> <method name="setListAdapter" return="void" abstract="false" @@ -28043,6 +28056,21 @@ <parameter name="adapter" type="android.widget.ListAdapter"> </parameter> </method> +<method name="setListShown" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="shown" type="boolean"> +</parameter> +<parameter name="animate" type="boolean"> +</parameter> +</method> <method name="setSelection" return="void" abstract="false" @@ -28152,7 +28180,7 @@ static="false" final="false" deprecated="not deprecated" - visibility="protected" + visibility="public" > <parameter name="id" type="int"> </parameter> @@ -223896,7 +223924,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="t" type="T"> +<parameter name="arg0" type="T"> </parameter> </method> </interface> @@ -247337,7 +247365,7 @@ <method name="availableProcessors" return="int" abstract="false" - native="false" + native="true" synchronized="false" static="false" final="false" diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4a47b66..91ff0a5 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4036,6 +4036,7 @@ public class Activity extends ContextThemeWrapper final void performStart() { mCalled = false; + mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); if (!mCalled) { throw new SuperNotCalledException( @@ -4074,6 +4075,8 @@ public class Activity extends ContextThemeWrapper final void performResume() { performRestart(); + mFragments.execPendingActions(); + mLastNonConfigurationInstances = null; // First call onResume() -before- setting mResumed, so we don't @@ -4091,6 +4094,7 @@ public class Activity extends ContextThemeWrapper mCalled = false; mFragments.dispatchResume(); + mFragments.execPendingActions(); onPostResume(); if (!mCalled) { diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java index 5e9aea5..c958e26 100644 --- a/core/java/android/app/BackStackEntry.java +++ b/core/java/android/app/BackStackEntry.java @@ -18,6 +18,7 @@ package android.app; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; import java.util.ArrayList; @@ -28,14 +29,30 @@ final class BackStackState implements Parcelable { final String mName; public BackStackState(FragmentManager fm, BackStackEntry bse) { - mOps = new int[bse.mNumOp*4]; + int numRemoved = 0; BackStackEntry.Op op = bse.mHead; + while (op != null) { + if (op.removed != null) numRemoved += op.removed.size(); + op = op.next; + } + mOps = new int[bse.mNumOp*5 + numRemoved]; + + op = bse.mHead; int pos = 0; while (op != null) { mOps[pos++] = op.cmd; mOps[pos++] = op.fragment.mIndex; mOps[pos++] = op.enterAnim; mOps[pos++] = op.exitAnim; + if (op.removed != null) { + final int N = op.removed.size(); + mOps[pos++] = N; + for (int i=0; i<N; i++) { + mOps[pos++] = op.removed.get(i).mIndex; + } + } else { + mOps[pos++] = 0; + } op = op.next; } mTransition = bse.mTransition; @@ -61,6 +78,13 @@ final class BackStackState implements Parcelable { op.fragment = f; op.enterAnim = mOps[pos++]; op.exitAnim = mOps[pos++]; + final int N = mOps[pos++]; + if (N > 0) { + op.removed = new ArrayList<Fragment>(N); + for (int i=0; i<N; i++) { + op.removed.add(fm.mActive.get(mOps[pos++])); + } + } bse.addOp(op); } bse.mTransition = mTransition; @@ -96,6 +120,8 @@ final class BackStackState implements Parcelable { * @hide Entry of an operation on the fragment back stack. */ final class BackStackEntry implements FragmentTransaction, Runnable { + static final String TAG = "BackStackEntry"; + final FragmentManager mManager; static final int OP_NULL = 0; @@ -265,11 +291,14 @@ final class BackStackEntry implements FragmentTransaction, Runnable { public void commit() { if (mCommitted) throw new IllegalStateException("commit already called"); + if (FragmentManager.DEBUG) Log.v(TAG, "Commit: " + this); mCommitted = true; - mManager.mActivity.mHandler.post(this); + mManager.enqueueAction(this); } public void run() { + if (FragmentManager.DEBUG) Log.v(TAG, "Run: " + this); + Op op = mHead; while (op != null) { switch (op.cmd) { @@ -286,6 +315,8 @@ final class BackStackEntry implements FragmentTransaction, Runnable { if (mManager.mAdded != null) { for (int i=0; i<mManager.mAdded.size(); i++) { Fragment old = mManager.mAdded.get(i); + if (FragmentManager.DEBUG) Log.v(TAG, + "OP_REPLACE: adding=" + f + " old=" + old); if (old.mContainerId == f.mContainerId) { if (op.removed == null) { op.removed = new ArrayList<Fragment>(); @@ -350,6 +381,8 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } public void popFromBackStack() { + if (FragmentManager.DEBUG) Log.v(TAG, "popFromBackStack: " + this); + Op op = mTail; while (op != null) { switch (op.cmd) { diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index c0ab0b5..b8eeb09 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -77,6 +77,10 @@ public class FragmentManager { static final boolean DEBUG = true; static final String TAG = "FragmentManager"; + ArrayList<Runnable> mPendingActions; + Runnable[] mTmpActions; + boolean mExecutingActions; + ArrayList<Fragment> mActive; ArrayList<Fragment> mAdded; ArrayList<Integer> mAvailIndices; @@ -91,6 +95,13 @@ public class FragmentManager { Bundle mStateBundle = null; SparseArray<Parcelable> mStateArray = null; + Runnable mExecCommit = new Runnable() { + @Override + public void run() { + execPendingActions(); + } + }; + Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { Animation animObj = fragment.onCreateAnimation(transitionStyle, enter, @@ -486,6 +497,52 @@ public class FragmentManager { return null; } + public void enqueueAction(Runnable action) { + synchronized (this) { + if (mPendingActions == null) { + mPendingActions = new ArrayList<Runnable>(); + } + mPendingActions.add(action); + if (mPendingActions.size() == 1) { + mActivity.mHandler.removeCallbacks(mExecCommit); + mActivity.mHandler.post(mExecCommit); + } + } + } + + /** + * Only call from main thread! + */ + public void execPendingActions() { + if (mExecutingActions) { + throw new IllegalStateException("Recursive entry to execPendingActions"); + } + + while (true) { + int numActions; + + synchronized (this) { + if (mPendingActions == null || mPendingActions.size() == 0) { + return; + } + + numActions = mPendingActions.size(); + if (mTmpActions == null || mTmpActions.length < numActions) { + mTmpActions = new Runnable[numActions]; + } + mPendingActions.toArray(mTmpActions); + mPendingActions.clear(); + mActivity.mHandler.removeCallbacks(mExecCommit); + } + + mExecutingActions = true; + for (int i=0; i<numActions; i++) { + mTmpActions[i].run(); + } + mExecutingActions = false; + } + } + public void addBackStackState(BackStackEntry state) { if (mBackStack == null) { mBackStack = new ArrayList<BackStackEntry>(); @@ -503,8 +560,9 @@ public class FragmentManager { return false; } final BackStackEntry bss = mBackStack.remove(last); - handler.post(new Runnable() { + enqueueAction(new Runnable() { public void run() { + if (DEBUG) Log.v(TAG, "Popping back stack state: " + bss); bss.popFromBackStack(); moveToState(mCurState, reverseTransit(bss.getTransition()), bss.getTransitionStyle(), true); @@ -526,9 +584,10 @@ public class FragmentManager { for (int i=mBackStack.size()-1; i>index; i--) { states.add(mBackStack.remove(i)); } - handler.post(new Runnable() { + enqueueAction(new Runnable() { public void run() { for (int i=0; i<states.size(); i++) { + if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i)); states.get(i).popFromBackStack(); } moveToState(mCurState, true); diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java index 0d8ec98..548dd61 100644 --- a/core/java/android/app/ListFragment.java +++ b/core/java/android/app/ListFragment.java @@ -21,9 +21,11 @@ import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.ListView; +import android.widget.TextView; /** * An fragment that displays a list of items by binding to a data source such as @@ -154,17 +156,28 @@ public class ListFragment extends Fragment { ListAdapter mAdapter; ListView mList; + View mEmptyView; + TextView mStandardEmptyView; + View mProgressContainer; + View mListContainer; + boolean mSetEmptyView; + boolean mListShown; public ListFragment() { } /** - * Provide default implementation to return a simple list view. + * Provide default implementation to return a simple list view. Subclasses + * can override to replace with their own layout. If doing so, the + * returned view hierarchy <em>must</em> have a ListView whose id + * is {@link android.R.id.list android.R.id.list} and can optionally + * have a sibling view id {@link android.R.id.empty android.R.id.empty} + * that is to be shown when the list is empty. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(com.android.internal.R.layout.list_content, + return inflater.inflate(com.android.internal.R.layout.list_content_rich, container, false); } @@ -247,6 +260,62 @@ public class ListFragment extends Fragment { } /** + * The default content for a ListFragment has a TextView that can + * be shown when the list is empty. If you would like to have it + * shown, call this method to supply the text it should use. + */ + public void setEmptyText(CharSequence text) { + ensureList(); + if (mStandardEmptyView == null) { + throw new IllegalStateException("Can't be used with a custom content view"); + } + if (!mSetEmptyView) { + mSetEmptyView = true; + mList.setEmptyView(mStandardEmptyView); + } + } + + /** + * Control whether the list is being displayed. You can make it not + * displayed if you are waiting for the initial data to show in it. During + * this time an indeterminant progress indicator will be shown instead. + * + * @param shown If true, the list view is shown; if false, the progress + * indicator. The initial value is true. + * @param animate If true, an animation will be used to transition to the + * new state. + */ + public void setListShown(boolean shown, boolean animate) { + ensureList(); + if (mProgressContainer == null) { + throw new IllegalStateException("Can't be used with a custom content view"); + } + if (mListShown == shown) { + return; + } + mListShown = shown; + if (shown) { + if (animate) { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_out)); + mListContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_in)); + } + mProgressContainer.setVisibility(View.GONE); + mListContainer.setVisibility(View.VISIBLE); + } else { + if (animate) { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_in)); + mListContainer.startAnimation(AnimationUtils.loadAnimation( + getActivity(), android.R.anim.fade_out)); + } + mProgressContainer.setVisibility(View.VISIBLE); + mListContainer.setVisibility(View.GONE); + } + } + + /** * Get the ListAdapter associated with this activity's ListView. */ public ListAdapter getListAdapter() { @@ -261,16 +330,33 @@ public class ListFragment extends Fragment { if (root == null) { throw new IllegalStateException("Content view not yet created"); } - View emptyView = root.findViewById(com.android.internal.R.id.empty); - mList = (ListView)root.findViewById(com.android.internal.R.id.list); - if (mList == null) { - throw new RuntimeException( - "Your content must have a ListView whose id attribute is " + - "'android.R.id.list'"); - } - if (emptyView != null) { - mList.setEmptyView(emptyView); + if (root instanceof ListView) { + mList = (ListView)root; + } else { + mStandardEmptyView = (TextView)root.findViewById( + com.android.internal.R.id.internalEmpty); + if (mStandardEmptyView == null) { + mEmptyView = root.findViewById(android.R.id.empty); + } + mProgressContainer = root.findViewById(com.android.internal.R.id.progressContainer); + mListContainer = root.findViewById(com.android.internal.R.id.listContainer); + View rawListView = root.findViewById(android.R.id.list); + if (!(rawListView instanceof ListView)) { + throw new RuntimeException( + "Content has view with id attribute 'android.R.id.list' " + + "that is not a ListView class"); + } + mList = (ListView)rawListView; + if (mList == null) { + throw new RuntimeException( + "Your content must have a ListView whose id attribute is " + + "'android.R.id.list'"); + } + if (mEmptyView != null) { + mList.setEmptyView(mEmptyView); + } } + mListShown = true; mList.setOnItemClickListener(mOnClickListener); if (mAdapter != null) { setListAdapter(mAdapter); diff --git a/core/java/android/app/LoaderManagingFragment.java b/core/java/android/app/LoaderManagingFragment.java index a8a5ca4..5d417a0 100644 --- a/core/java/android/app/LoaderManagingFragment.java +++ b/core/java/android/app/LoaderManagingFragment.java @@ -44,7 +44,7 @@ public abstract class LoaderManagingFragment<D> extends Fragment * when the new loader completes it's work. The callback will be delivered before the old loader * is destroyed. */ - protected Loader<D> startLoading(int id, Bundle args) { + public Loader<D> startLoading(int id, Bundle args) { LoaderInfo<D> info = mLoaders.get(id); if (info != null) { // Keep track of the previous instance of this loader so we can destroy diff --git a/core/res/res/layout/list_content_rich.xml b/core/res/res/layout/list_content_rich.xml new file mode 100644 index 0000000..1414032 --- /dev/null +++ b/core/res/res/layout/list_content_rich.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 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. +*/ +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout android:id="@+id/progressContainer" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:gravity="center"> + + <ProgressBar style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="@string/loading" + android:paddingTop="4dip" + android:singleLine="true" /> + + </LinearLayout> + + <FrameLayout android:id="@+id/listContainer" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ListView android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:drawSelectorOnTop="false" /> + <TextView android:id="@+android:id/internalEmpty" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </FrameLayout> + +</FrameLayout> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6b371df..de1ea68 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1919,6 +1919,9 @@ combined with setIcon(android.R.drawable.ic_dialog_alert) --> <string name="dialog_alert_title">Attention</string> + <!-- Text shown by list fragment when waiting for data to display. --> + <string name="loading">Loading...</string> + <!-- Default text for a button that can be toggled on and off. --> <string name="capital_on">ON</string> <!-- Default text for a button that can be toggled on and off. --> |
