Introducing Fragment.
Basic implementation of an API for organizing a single activity into separate,
discrete pieces. Currently supports adding and removing fragments, and
performing basic lifecycle callbacks on them.
Change-Id: I6ea8e6bdb04d93f8105c2e983fe9b6532422de34
diff --git a/api/current.xml b/api/current.xml
index edf9787..db75010 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -20304,6 +20304,17 @@
<parameter name="view" type="android.view.View">
</parameter>
</method>
+<method name="openFragmentTransaction"
+ return="android.app.FragmentTransaction"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="openOptionsMenu"
return="void"
abstract="false"
@@ -24658,6 +24669,300 @@
</parameter>
</method>
</class>
+<class name="Fragment"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.content.ComponentCallbacks">
+</implements>
+<constructor name="Fragment"
+ type="android.app.Fragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="Fragment"
+ type="android.app.Fragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</constructor>
+<method name="getActivity"
+ return="android.app.Activity"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onAttach"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="activity" type="android.app.Activity">
+</parameter>
+</method>
+<method name="onConfigurationChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="newConfig" type="android.content.res.Configuration">
+</parameter>
+</method>
+<method name="onCreate"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onCreateView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="inflater" type="android.view.LayoutInflater">
+</parameter>
+<parameter name="container" type="android.view.ViewGroup">
+</parameter>
+</method>
+<method name="onDestroy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onDetach"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onLowMemory"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPause"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRestart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRestoreInstanceState"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onResume"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRetainNonConfigurationInstance"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onSaveInstanceState"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="outState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onStart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onStop"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<interface name="FragmentTransaction"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="add"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="containerViewId" type="int">
+</parameter>
+</method>
+<method name="add"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="containerViewId" type="int">
+</parameter>
+</method>
+<method name="commit"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="remove"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
+</interface>
<class name="Instrumentation"
extends="java.lang.Object"
abstract="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a962391..15bf242 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -650,6 +650,65 @@
private CharSequence mTitle;
private int mTitleColor = 0;
+ final FragmentManager mFragments = new FragmentManager();
+
+ private final class FragmentTransactionImpl implements FragmentTransaction {
+ ArrayList<Fragment> mAdded;
+ ArrayList<Fragment> mRemoved;
+
+ public FragmentTransaction add(Fragment fragment, int containerViewId) {
+ return add(fragment, null, containerViewId);
+ }
+
+ public FragmentTransaction add(Fragment fragment, String name, int containerViewId) {
+ if (fragment.mActivity != null) {
+ throw new IllegalStateException("Fragment already added: " + fragment);
+ }
+ if (name != null) {
+ fragment.mName = name;
+ }
+ if (mRemoved != null) {
+ mRemoved.remove(fragment);
+ }
+ if (mAdded == null) {
+ mAdded = new ArrayList<Fragment>();
+ }
+ fragment.mContainerId = containerViewId;
+ mAdded.add(fragment);
+ return this;
+ }
+
+ public FragmentTransaction remove(Fragment fragment) {
+ if (fragment.mActivity == null) {
+ throw new IllegalStateException("Fragment not added: " + fragment);
+ }
+ if (mAdded != null) {
+ mAdded.remove(fragment);
+ }
+ if (mRemoved == null) {
+ mRemoved = new ArrayList<Fragment>();
+ }
+ mRemoved.add(fragment);
+ return this;
+ }
+
+ public void commit() {
+ if (mRemoved != null) {
+ for (int i=mRemoved.size()-1; i>=0; i--) {
+ mFragments.removeFragment(mRemoved.get(i));
+ }
+ }
+ if (mAdded != null) {
+ for (int i=mAdded.size()-1; i>=0; i--) {
+ mFragments.addFragment(mAdded.get(i));
+ }
+ }
+ if (mFragments != null) {
+ mFragments.moveToState(mFragments.mCurState);
+ }
+ }
+ }
+
private static final class ManagedCursor {
ManagedCursor(Cursor cursor) {
mCursor = cursor;
@@ -1464,6 +1523,14 @@
}
/**
+ * Start a series of edit operations on the Fragments associated with
+ * this activity.
+ */
+ public FragmentTransaction openFragmentTransaction() {
+ return new FragmentTransactionImpl();
+ }
+
+ /**
* Wrapper around
* {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
* that gives the resulting {@link Cursor} to call
@@ -3743,6 +3810,8 @@
Configuration config) {
attachBaseContext(context);
+ mFragments.attachActivity(this);
+
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
@@ -3776,6 +3845,11 @@
return mParent != null ? mParent.getActivityToken() : mToken;
}
+ final void performCreate(Bundle icicle) {
+ onCreate(icicle);
+ mFragments.dispatchCreate(icicle);
+ }
+
final void performStart() {
mCalled = false;
mInstrumentation.callActivityOnStart(this);
@@ -3784,6 +3858,7 @@
"Activity " + mComponent.toShortString() +
" did not call through to super.onStart()");
}
+ mFragments.dispatchStart();
}
final void performRestart() {
@@ -3830,6 +3905,9 @@
// Now really resume, and install the current status bar and menu.
mResumed = true;
mCalled = false;
+
+ mFragments.dispatchResume();
+
onPostResume();
if (!mCalled) {
throw new SuperNotCalledException(
@@ -3839,6 +3917,7 @@
}
final void performPause() {
+ mFragments.dispatchPause();
onPause();
}
@@ -3853,6 +3932,8 @@
mWindow.closeAllPanels();
}
+ mFragments.dispatchStop();
+
mCalled = false;
mInstrumentation.callActivityOnStop(this);
if (!mCalled) {
@@ -3877,6 +3958,11 @@
mResumed = false;
}
+ final void performDestroy() {
+ mFragments.dispatchDestroy();
+ onDestroy();
+ }
+
final boolean isResumed() {
return mResumed;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5468d52..87b77d65 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3643,7 +3643,7 @@
}
try {
r.activity.mCalled = false;
- r.activity.onDestroy();
+ mInstrumentation.callActivityOnDestroy(r.activity);
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + safeToComponentShortString(r.intent) +
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
new file mode 100644
index 0000000..c0dc869
--- /dev/null
+++ b/core/java/android/app/Fragment.java
@@ -0,0 +1,119 @@
+/*
+ * 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 android.app;
+
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A Fragment is a piece of an application's user interface or behavior
+ * that can be placed in an {@link Activity}.
+ */
+public class Fragment implements ComponentCallbacks {
+ static final int INITIALIZING = 0; // Not yet created.
+ static final int CREATED = 1; // Created.
+ static final int STARTED = 2; // Created and started, not resumed.
+ static final int RESUMED = 3; // Created started and resumed.
+
+ String mName;
+
+ int mState = INITIALIZING;
+ Activity mActivity;
+
+ boolean mCalled;
+ int mContainerId;
+
+ ViewGroup mContainer;
+ View mView;
+
+ public Fragment() {
+ }
+
+ public Fragment(String name) {
+ mName = name;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public Activity getActivity() {
+ return mActivity;
+ }
+
+ public void onAttach(Activity activity) {
+ mCalled = true;
+ }
+
+ public void onCreate(Bundle savedInstanceState) {
+ mCalled = true;
+ }
+
+ public View onCreateView(LayoutInflater inflater, ViewGroup container) {
+ return null;
+ }
+
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ }
+
+ public void onStart() {
+ mCalled = true;
+ }
+
+ public void onRestart() {
+ mCalled = true;
+ }
+
+ public void onResume() {
+ mCalled = true;
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ mCalled = true;
+ }
+
+ public Object onRetainNonConfigurationInstance() {
+ return null;
+ }
+
+ public void onPause() {
+ mCalled = true;
+ }
+
+ public void onStop() {
+ mCalled = true;
+ }
+
+ public void onLowMemory() {
+ mCalled = true;
+ }
+
+ public void onDestroy() {
+ mCalled = true;
+ }
+
+ public void onDetach() {
+ mCalled = true;
+ }
+}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
new file mode 100644
index 0000000..d5e49cf
--- /dev/null
+++ b/core/java/android/app/FragmentManager.java
@@ -0,0 +1,183 @@
+/*
+ * 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 android.app;
+
+import android.os.Bundle;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Container for fragments associated with an activity.
+ */
+class FragmentManager {
+ final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
+
+ int mCurState = Fragment.INITIALIZING;
+ Activity mActivity;
+
+ void moveToState(Fragment f, int newState) {
+ if (f.mState < newState) {
+ switch (f.mState) {
+ case Fragment.INITIALIZING:
+ f.mActivity = mActivity;
+ f.mCalled = false;
+ f.onAttach(mActivity);
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onAttach()");
+ }
+ f.mCalled = false;
+ f.onCreate(null);
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onCreate()");
+ }
+
+ ViewGroup container = null;
+ if (f.mContainerId != 0) {
+ container = (ViewGroup)mActivity.findViewById(f.mContainerId);
+ if (container == null) {
+ throw new IllegalArgumentException("New view found for id 0x"
+ + Integer.toHexString(f.mContainerId)
+ + " for fragment " + f);
+ }
+ }
+ f.mContainer = container;
+ f.mView = f.onCreateView(mActivity.getLayoutInflater(), container);
+ if (container != null && f.mView != null) {
+ container.addView(f.mView);
+ }
+
+ case Fragment.CREATED:
+ if (newState > Fragment.CREATED) {
+ f.mCalled = false;
+ f.onStart();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onStart()");
+ }
+ }
+ case Fragment.STARTED:
+ if (newState > Fragment.STARTED) {
+ f.mCalled = false;
+ f.onResume();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onResume()");
+ }
+ }
+ }
+ } else if (f.mState > newState) {
+ switch (f.mState) {
+ case Fragment.RESUMED:
+ if (newState < Fragment.RESUMED) {
+ f.mCalled = false;
+ f.onPause();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onPause()");
+ }
+ }
+ case Fragment.STARTED:
+ if (newState < Fragment.STARTED) {
+ f.mCalled = false;
+ f.onStop();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onStop()");
+ }
+ }
+ case Fragment.CREATED:
+ if (newState < Fragment.CREATED) {
+ if (f.mContainer != null && f.mView != null) {
+ f.mContainer.removeView(f.mView);
+ }
+ f.mContainer = null;
+ f.mView = null;
+
+ f.mCalled = false;
+ f.onDestroy();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onDestroy()");
+ }
+ f.mCalled = false;
+ f.onDetach();
+ if (!f.mCalled) {
+ throw new SuperNotCalledException("Fragment " + f
+ + " did not call through to super.onDetach()");
+ }
+ f.mActivity = null;
+ }
+ }
+ }
+
+ f.mState = newState;
+ }
+
+ void moveToState(int newState) {
+ if (mActivity == null && newState != Fragment.INITIALIZING) {
+ throw new IllegalStateException("No activity");
+ }
+
+ mCurState = newState;
+ for (int i=0; i<mFragments.size(); i++) {
+ Fragment f = mFragments.get(i);
+ moveToState(f, newState);
+ }
+ }
+
+ public void addFragment(Fragment fragment) {
+ mFragments.add(fragment);
+ }
+
+ public void removeFragment(Fragment fragment) {
+ mFragments.remove(fragment);
+ moveToState(fragment, Fragment.INITIALIZING);
+ }
+
+ public void attachActivity(Activity activity) {
+ if (mActivity != null) throw new IllegalStateException();
+ mActivity = activity;
+ }
+
+ public void dispatchCreate(Bundle state) {
+ moveToState(Fragment.CREATED);
+ }
+
+ public void dispatchStart() {
+ moveToState(Fragment.STARTED);
+ }
+
+ public void dispatchResume() {
+ moveToState(Fragment.RESUMED);
+ }
+
+ public void dispatchPause() {
+ moveToState(Fragment.STARTED);
+ }
+
+ public void dispatchStop() {
+ moveToState(Fragment.CREATED);
+ }
+
+ public void dispatchDestroy() {
+ moveToState(Fragment.INITIALIZING);
+ mActivity = null;
+ }
+}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
new file mode 100644
index 0000000..f97e510
--- /dev/null
+++ b/core/java/android/app/FragmentTransaction.java
@@ -0,0 +1,11 @@
+package android.app;
+
+/**
+ * API for performing a set of Fragment operations.
+ */
+public interface FragmentTransaction {
+ public FragmentTransaction add(Fragment fragment, int containerViewId);
+ public FragmentTransaction add(Fragment fragment, String name, int containerViewId);
+ public FragmentTransaction remove(Fragment fragment);
+ public void commit();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b8c3aa3..7ed7c49 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1072,7 +1072,7 @@
}
}
- activity.onDestroy();
+ activity.performDestroy();
if (mActivityMonitors != null) {
synchronized (mSync) {