summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt22
-rw-r--r--core/java/android/app/Activity.java218
-rw-r--r--core/java/android/app/ActivityManager.java1
-rw-r--r--core/java/android/app/ActivityManagerNative.java72
-rw-r--r--core/java/android/app/IActivityManager.java8
-rw-r--r--core/java/android/app/TaskStackBuilder.java212
-rw-r--r--core/java/android/content/pm/ActivityInfo.java8
-rw-r--r--core/java/android/content/pm/PackageParser.java28
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java14
-rw-r--r--core/res/res/values/attrs_manifest.xml5
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java107
12 files changed, 683 insertions, 14 deletions
diff --git a/api/current.txt b/api/current.txt
index d8461e7..80918b8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -727,6 +727,7 @@ package android {
field public static final int panelColorForeground = 16842848; // 0x1010060
field public static final int panelFullBackground = 16842847; // 0x101005f
field public static final int panelTextAppearance = 16842850; // 0x1010062
+ field public static final int parentActivityName = 16843696; // 0x10103b0
field public static final deprecated int password = 16843100; // 0x101015c
field public static final int path = 16842794; // 0x101002a
field public static final int pathPattern = 16842796; // 0x101002c
@@ -2578,6 +2579,7 @@ package android.app {
method public java.lang.String getLocalClassName();
method public android.view.MenuInflater getMenuInflater();
method public final android.app.Activity getParent();
+ method public android.content.Intent getParentActivityIntent();
method public android.content.SharedPreferences getPreferences(int);
method public int getRequestedOrientation();
method public int getTaskId();
@@ -2594,6 +2596,8 @@ package android.app {
method public boolean isTaskRoot();
method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public boolean moveTaskToBack(boolean);
+ method public boolean navigateUpTo(android.content.Intent);
+ method public boolean navigateUpToFromChild(android.app.Activity, android.content.Intent);
method public void onActionModeFinished(android.view.ActionMode);
method public void onActionModeStarted(android.view.ActionMode);
method protected void onActivityResult(int, int, android.content.Intent);
@@ -2610,6 +2614,7 @@ package android.app {
method public java.lang.CharSequence onCreateDescription();
method protected deprecated android.app.Dialog onCreateDialog(int);
method protected deprecated android.app.Dialog onCreateDialog(int, android.os.Bundle);
+ method public void onCreateNavigateUpTaskStack(android.app.TaskStackBuilder);
method public boolean onCreateOptionsMenu(android.view.Menu);
method public boolean onCreatePanelMenu(int, android.view.Menu);
method public android.view.View onCreatePanelView(int);
@@ -2627,6 +2632,8 @@ package android.app {
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
+ method public boolean onNavigateUp();
+ method public boolean onNavigateUpFromChild(android.app.Activity);
method protected void onNewIntent(android.content.Intent);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
@@ -2636,6 +2643,7 @@ package android.app {
method protected void onPostResume();
method protected deprecated void onPrepareDialog(int, android.app.Dialog);
method protected deprecated void onPrepareDialog(int, android.app.Dialog, android.os.Bundle);
+ method public void onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder);
method public boolean onPrepareOptionsMenu(android.view.Menu);
method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
method protected void onRestart();
@@ -2686,6 +2694,7 @@ package android.app {
method public void setTitleColor(int);
method public void setVisible(boolean);
method public final void setVolumeControlStream(int);
+ method public boolean shouldUpRecreateTask(android.content.Intent);
method public final deprecated void showDialog(int);
method public final deprecated boolean showDialog(int, android.os.Bundle);
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
@@ -3932,6 +3941,18 @@ package android.app {
method public void setDefaultTab(int);
}
+ public class TaskStackBuilder implements java.lang.Iterable {
+ method public android.app.TaskStackBuilder addNextIntent(android.content.Intent);
+ method public android.app.TaskStackBuilder addParentStack(android.app.Activity);
+ method public android.app.TaskStackBuilder addParentStack(java.lang.Class<?>);
+ method public static android.app.TaskStackBuilder from(android.content.Context);
+ method public android.content.Intent getIntent(int);
+ method public int getIntentCount();
+ method public android.app.PendingIntent getPendingIntent(int, int);
+ method public java.util.Iterator<android.content.Intent> iterator();
+ method public void startActivities();
+ }
+
public class TimePickerDialog extends android.app.AlertDialog implements android.content.DialogInterface.OnClickListener android.widget.TimePicker.OnTimeChangedListener {
ctor public TimePickerDialog(android.content.Context, android.app.TimePickerDialog.OnTimeSetListener, int, int, boolean);
ctor public TimePickerDialog(android.content.Context, int, android.app.TimePickerDialog.OnTimeSetListener, int, int, boolean);
@@ -6102,6 +6123,7 @@ package android.content.pm {
field public int configChanges;
field public int flags;
field public int launchMode;
+ field public java.lang.String parentActivityName;
field public java.lang.String permission;
field public int screenOrientation;
field public int softInputMode;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b277efb..3e123ba 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -29,6 +29,8 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -65,13 +67,13 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
-import android.view.WindowManagerImpl;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewManager;
import android.view.Window;
import android.view.WindowManager;
+import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
@@ -704,6 +706,7 @@ public class Activity extends ContextThemeWrapper
/*package*/ boolean mVisibleFromServer = false;
/*package*/ boolean mVisibleFromClient = true;
/*package*/ ActionBarImpl mActionBar = null;
+ private boolean mEnableDefaultActionBarUp;
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -865,6 +868,13 @@ public class Activity extends ContextThemeWrapper
if (mLastNonConfigurationInstances != null) {
mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
}
+ if (mActivityInfo.parentActivityName != null) {
+ if (mActionBar == null) {
+ mEnableDefaultActionBarUp = true;
+ } else {
+ mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
+ }
+ }
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
@@ -1829,6 +1839,7 @@ public class Activity extends ContextThemeWrapper
}
mActionBar = new ActionBarImpl(this);
+ mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
}
/**
@@ -2630,7 +2641,7 @@ public class Activity extends ContextThemeWrapper
* facilities.
*
* <p>Derived classes should call through to the base class for it to
- * perform the default menu handling.
+ * perform the default menu handling.</p>
*
* @param item The menu item that was selected.
*
@@ -2643,10 +2654,105 @@ public class Activity extends ContextThemeWrapper
if (mParent != null) {
return mParent.onOptionsItemSelected(item);
}
+ if (item.getItemId() == android.R.id.home && mActionBar != null &&
+ (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ if (mParent == null) {
+ onNavigateUp();
+ } else {
+ mParent.onNavigateUpFromChild(this);
+ }
+ return true;
+ }
return false;
}
/**
+ * This method is called whenever the user chooses to navigate Up within your application's
+ * activity hierarchy from the action bar.
+ *
+ * <p>If the attribute {@link android.R.attr#parentActivityName parentActivityName}
+ * was specified in the manifest for this activity or an activity-alias to it,
+ * default Up navigation will be handled automatically. If any activity
+ * along the parent chain requires extra Intent arguments, the Activity subclass
+ * should override the method {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}
+ * to supply those arguments.</p>
+ *
+ * <p>See <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a>
+ * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a>
+ * from the design guide for more information about navigating within your app.</p>
+ *
+ * <p>See the {@link TaskStackBuilder} class and the Activity methods
+ * {@link #getParentActivityIntent()}, {@link #shouldUpRecreateTask(Intent)}, and
+ * {@link #navigateUpTo(Intent)} for help implementing custom Up navigation.
+ * The AppNavigation sample application in the Android SDK is also available for reference.</p>
+ *
+ * @return true if Up navigation completed successfully and this Activity was finished,
+ * false otherwise.
+ */
+ public boolean onNavigateUp() {
+ // Automatically handle hierarchical Up navigation if the proper
+ // metadata is available.
+ Intent upIntent = getParentActivityIntent();
+ if (upIntent != null) {
+ if (shouldUpRecreateTask(upIntent)) {
+ TaskStackBuilder b = TaskStackBuilder.from(this);
+ onCreateNavigateUpTaskStack(b);
+ onPrepareNavigateUpTaskStack(b);
+ b.startActivities();
+ finish();
+ } else {
+ navigateUpTo(upIntent);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This is called when a child activity of this one attempts to navigate up.
+ * The default implementation simply calls onNavigateUp() on this activity (the parent).
+ *
+ * @param child The activity making the call.
+ */
+ public boolean onNavigateUpFromChild(Activity child) {
+ return onNavigateUp();
+ }
+
+ /**
+ * Define the synthetic task stack that will be generated during Up navigation from
+ * a different task.
+ *
+ * <p>The default implementation of this method adds the parent chain of this activity
+ * as specified in the manifest to the supplied {@link TaskStackBuilder}. Applications
+ * may choose to override this method to construct the desired task stack in a different
+ * way.</p>
+ *
+ * <p>Applications that wish to supply extra Intent parameters to the parent stack defined
+ * by the manifest should override {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}.</p>
+ *
+ * @param builder An empty TaskStackBuilder - the application should add intents representing
+ * the desired task stack
+ */
+ public void onCreateNavigateUpTaskStack(TaskStackBuilder builder) {
+ builder.addParentStack(this);
+ }
+
+ /**
+ * Prepare the synthetic task stack that will be generated during Up navigation
+ * from a different task.
+ *
+ * <p>This method receives the {@link TaskStackBuilder} with the constructed series of
+ * Intents as generated by {@link #onCreateNavigateUpTaskStack(TaskStackBuilder)}.
+ * If any extra data should be added to these intents before launching the new task,
+ * the application should override this method and add that data here.</p>
+ *
+ * @param builder A TaskStackBuilder that has been populated with Intents by
+ * onCreateNavigateUpTaskStack.
+ */
+ public void onPrepareNavigateUpTaskStack(TaskStackBuilder builder) {
+ }
+
+ /**
* This hook is called whenever the options menu is being closed (either by the user canceling
* the menu with the back/menu button, or when an item is selected).
*
@@ -4658,6 +4764,114 @@ public class Activity extends ContextThemeWrapper
public void onActionModeFinished(ActionMode mode) {
}
+ /**
+ * Returns true if the app should recreate the task when navigating 'up' from this activity
+ * by using targetIntent.
+ *
+ * <p>If this method returns false the app can trivially call
+ * {@link #navigateUpTo(Intent)} using the same parameters to correctly perform
+ * up navigation. If this method returns false, the app should synthesize a new task stack
+ * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.</p>
+ *
+ * @param targetIntent An intent representing the target destination for up navigation
+ * @return true if navigating up should recreate a new task stack, false if the same task
+ * should be used for the destination
+ */
+ public boolean shouldUpRecreateTask(Intent targetIntent) {
+ try {
+ PackageManager pm = getPackageManager();
+ ComponentName cn = targetIntent.getComponent();
+ if (cn == null) {
+ cn = targetIntent.resolveActivity(pm);
+ }
+ ActivityInfo info = pm.getActivityInfo(cn, 0);
+ if (info.taskAffinity == null) {
+ return false;
+ }
+ return !ActivityManagerNative.getDefault()
+ .targetTaskAffinityMatchesActivity(mToken, info.taskAffinity);
+ } catch (RemoteException e) {
+ return false;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Navigate from this activity to the activity specified by upIntent, finishing this activity
+ * in the process. If the activity indicated by upIntent already exists in the task's history,
+ * this activity and all others before the indicated activity in the history stack will be
+ * finished. If the indicated activity does not appear in the history stack, this is equivalent
+ * to simply calling finish() on this activity.
+ *
+ * <p>This method should be used when performing up navigation from within the same task
+ * as the destination. If up navigation should cross tasks in some cases, see
+ * {@link #shouldUpRecreateTask(Intent)}.</p>
+ *
+ * @param upIntent An intent representing the target destination for up navigation
+ *
+ * @return true if up navigation successfully reached the activity indicated by upIntent and
+ * upIntent was delivered to it. false if an instance of the indicated activity could
+ * not be found and this activity was simply finished normally.
+ */
+ public boolean navigateUpTo(Intent upIntent) {
+ if (mParent == null) {
+ ComponentName destInfo = upIntent.getComponent();
+ if (destInfo == null) {
+ destInfo = upIntent.resolveActivity(getPackageManager());
+ if (destInfo == null) {
+ return false;
+ }
+ upIntent = new Intent(upIntent);
+ upIntent.setComponent(destInfo);
+ }
+ int resultCode;
+ Intent resultData;
+ synchronized (this) {
+ resultCode = mResultCode;
+ resultData = mResultData;
+ }
+ if (resultData != null) {
+ resultData.setAllowFds(false);
+ }
+ try {
+ return ActivityManagerNative.getDefault().navigateUpTo(mToken, upIntent,
+ resultCode, resultData);
+ } catch (RemoteException e) {
+ return false;
+ }
+ } else {
+ return mParent.navigateUpToFromChild(this, upIntent);
+ }
+ }
+
+ /**
+ * This is called when a child activity of this one calls its
+ * {@link #navigateUpTo} method. The default implementation simply calls
+ * navigateUpTo(upIntent) on this activity (the parent).
+ *
+ * @param child The activity making the call.
+ * @param upIntent An intent representing the target destination for up navigation
+ *
+ * @return true if up navigation successfully reached the activity indicated by upIntent and
+ * upIntent was delivered to it. false if an instance of the indicated activity could
+ * not be found and this activity was simply finished normally.
+ */
+ public boolean navigateUpToFromChild(Activity child, Intent upIntent) {
+ return navigateUpTo(upIntent);
+ }
+
+ /**
+ * Obtain an {@link Intent} that will launch an explicit target activity specified by
+ * this activity's logical parent. The logical parent is named in the application's manifest
+ * by the {@link android.R.attr#parentActivityName parentActivityName} attribute.
+ *
+ * @return a new Intent targeting the defined parent of this activity
+ */
+ public Intent getParentActivityIntent() {
+ return new Intent().setClassName(this, mActivityInfo.parentActivityName);
+ }
+
// ------------------ Internal API ------------------
final void setParent(Activity parent) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 531a695..11b4c3a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1774,5 +1774,4 @@ public class ActivityManager {
return false;
}
}
-
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 5917cbf..000abc5 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -17,10 +17,10 @@
package android.app;
import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.IIntentSender;
-import android.content.IIntentReceiver;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
@@ -32,11 +32,11 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
-import android.os.Parcelable;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.text.TextUtils;
@@ -1605,6 +1605,31 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ String destAffinity = data.readString();
+ boolean res = targetTaskAffinityMatchesActivity(token, destAffinity);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
+ return true;
+ }
+
+ case NAVIGATE_UP_TO_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ Intent target = Intent.CREATOR.createFromParcel(data);
+ int resultCode = data.readInt();
+ Intent resultData = null;
+ if (data.readInt() != 0) {
+ resultData = Intent.CREATOR.createFromParcel(data);
+ }
+ boolean res = navigateUpTo(token, target, resultCode, resultData);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -3662,5 +3687,42 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ data.writeString(destAffinity);
+ mRemote.transact(TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ data.recycle();
+ reply.recycle();
+ return result;
+ }
+
+ public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ target.writeToParcel(data, 0);
+ data.writeInt(resultCode);
+ if (resultData != null) {
+ data.writeInt(1);
+ resultData.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ mRemote.transact(NAVIGATE_UP_TO_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ data.recycle();
+ reply.recycle();
+ return result;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 2b1eb43..0f287c1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -341,6 +341,12 @@ public interface IActivityManager extends IInterface {
public void dismissKeyguardOnNextActivity() throws RemoteException;
+ public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity)
+ throws RemoteException;
+
+ public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData)
+ throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -578,4 +584,6 @@ public interface IActivityManager extends IInterface {
int GET_MY_MEMORY_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+142;
int KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+143;
int GET_CURRENT_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+144;
+ int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145;
+ int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146;
}
diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java
new file mode 100644
index 0000000..ee7c6ad
--- /dev/null
+++ b/core/java/android/app/TaskStackBuilder.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2012 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Utility class for constructing synthetic back stacks for cross-task navigation
+ * on Android 3.0 and newer.
+ *
+ * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
+ * app navigation using the back key changed. The back key's behavior is local
+ * to the current task and does not capture navigation across different tasks.
+ * Navigating across tasks and easily reaching the previous task is accomplished
+ * through the "recents" UI, accessible through the software-provided Recents key
+ * on the navigation or system bar. On devices with the older hardware button configuration
+ * the recents UI can be accessed with a long press on the Home key.</p>
+ *
+ * <p>When crossing from one task stack to another post-Android 3.0,
+ * the application should synthesize a back stack/history for the new task so that
+ * the user may navigate out of the new task and back to the Launcher by repeated
+ * presses of the back key. Back key presses should not navigate across task stacks.</p>
+ *
+ * <p>TaskStackBuilder provides a way to obey the correct conventions
+ * around cross-task navigation.</p>
+ *
+ * <div class="special reference">
+ * <h3>About Navigation</h3>
+ * For more detailed information about tasks, the back stack, and navigation design guidelines,
+ * please read
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a>
+ * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a>
+ * from the design guide.
+ * </div>
+ */
+public class TaskStackBuilder implements Iterable<Intent> {
+ private static final String TAG = "TaskStackBuilder";
+
+ private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
+ private final Context mSourceContext;
+
+ private TaskStackBuilder(Context a) {
+ mSourceContext = a;
+ }
+
+ /**
+ * Return a new TaskStackBuilder for launching a fresh task stack consisting
+ * of a series of activities.
+ *
+ * @param context The context that will launch the new task stack or generate a PendingIntent
+ * @return A new TaskStackBuilder
+ */
+ public static TaskStackBuilder from(Context context) {
+ return new TaskStackBuilder(context);
+ }
+
+ /**
+ * Add a new Intent to the task stack. The most recently added Intent will invoke
+ * the Activity at the top of the final task stack.
+ *
+ * @param nextIntent Intent for the next Activity in the synthesized task stack
+ * @return This TaskStackBuilder for method chaining
+ */
+ public TaskStackBuilder addNextIntent(Intent nextIntent) {
+ mIntents.add(nextIntent);
+ return this;
+ }
+
+ /**
+ * Add the activity parent chain as specified by the
+ * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity
+ * (or activity-alias) element in the application's manifest to the task stack builder.
+ *
+ * @param sourceActivity All parents of this activity will be added
+ * @return This TaskStackBuilder for method chaining
+ */
+ public TaskStackBuilder addParentStack(Activity sourceActivity) {
+ final int insertAt = mIntents.size();
+ Intent parent = sourceActivity.getParentActivityIntent();
+ PackageManager pm = sourceActivity.getPackageManager();
+ while (parent != null) {
+ mIntents.add(insertAt, parent);
+ try {
+ ActivityInfo info = pm.getActivityInfo(parent.getComponent(), 0);
+ String parentActivity = info.parentActivityName;
+ if (parentActivity != null) {
+ parent = new Intent().setComponent(
+ new ComponentName(mSourceContext, parentActivity));
+ } else {
+ parent = null;
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
+ throw new IllegalArgumentException(e);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Add the activity parent chain as specified by the
+ * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity
+ * (or activity-alias) element in the application's manifest to the task stack builder.
+ *
+ * @param sourceActivityClass All parents of this activity will be added
+ * @return This TaskStackBuilder for method chaining
+ */
+ public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
+ final int insertAt = mIntents.size();
+ PackageManager pm = mSourceContext.getPackageManager();
+ try {
+ ActivityInfo info = pm.getActivityInfo(
+ new ComponentName(mSourceContext, sourceActivityClass), 0);
+ String parentActivity = info.parentActivityName;
+ Intent parent = new Intent().setComponent(
+ new ComponentName(mSourceContext, parentActivity));
+ while (parent != null) {
+ mIntents.add(insertAt, parent);
+ info = pm.getActivityInfo(parent.getComponent(), 0);
+ parentActivity = info.parentActivityName;
+ if (parentActivity != null) {
+ parent = new Intent().setComponent(
+ new ComponentName(mSourceContext, parentActivity));
+ } else {
+ parent = null;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
+ throw new IllegalArgumentException(e);
+ }
+ return this;
+ }
+
+ /**
+ * @return the number of intents added so far.
+ */
+ public int getIntentCount() {
+ return mIntents.size();
+ }
+
+ /**
+ * Get the intent at the specified index.
+ * Useful if you need to modify the flags or extras of an intent that was previously added,
+ * for example with {@link #addParentStack(Activity)}.
+ *
+ * @param index Index from 0-getIntentCount()
+ * @return the intent at position index
+ */
+ public Intent getIntent(int index) {
+ return mIntents.get(index);
+ }
+
+ public Iterator<Intent> iterator() {
+ return mIntents.iterator();
+ }
+
+ /**
+ * Start the task stack constructed by this builder.
+ */
+ public void startActivities() {
+ if (mIntents.isEmpty()) {
+ throw new IllegalStateException(
+ "No intents added to TaskStackBuilder; cannot startActivities");
+ }
+
+ Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
+ intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ mSourceContext.startActivities(intents);
+ }
+
+ /**
+ * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
+ *
+ * @param requestCode Private request code for the sender
+ * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
+ * {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
+ * {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
+ * {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
+ * intent that can be supplied when the actual send happens.
+ * @return The obtained PendingIntent
+ */
+ public PendingIntent getPendingIntent(int requestCode, int flags) {
+ Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
+ return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags);
+ }
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0e6694d..6b16e74 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -450,6 +450,11 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1;
+ /**
+ * If defined, the activity named here is the logical parent of this activity.
+ */
+ public String parentActivityName;
+
public ActivityInfo() {
}
@@ -465,6 +470,7 @@ public class ActivityInfo extends ComponentInfo
configChanges = orig.configChanges;
softInputMode = orig.softInputMode;
uiOptions = orig.uiOptions;
+ parentActivityName = orig.parentActivityName;
}
/**
@@ -524,6 +530,7 @@ public class ActivityInfo extends ComponentInfo
dest.writeInt(configChanges);
dest.writeInt(softInputMode);
dest.writeInt(uiOptions);
+ dest.writeString(parentActivityName);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -548,5 +555,6 @@ public class ActivityInfo extends ComponentInfo
configChanges = source.readInt();
softInputMode = source.readInt();
uiOptions = source.readInt();
+ parentActivityName = source.readString();
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a79b86a..15ccda3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2030,6 +2030,19 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivity_uiOptions,
a.info.applicationInfo.uiOptions);
+ String parentName = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivity_parentActivityName, 0);
+ if (parentName != null) {
+ String parentClassName = buildClassName(a.info.packageName, parentName, outError);
+ if (outError[0] == null) {
+ a.info.parentActivityName = parentClassName;
+ } else {
+ Log.e(TAG, "Activity " + a.info.name + " specified invalid parentActivityName " +
+ parentName);
+ outError[0] = null;
+ }
+ }
+
String str;
str = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestActivity_permission, 0);
@@ -2274,6 +2287,7 @@ public class PackageParser {
info.theme = target.info.theme;
info.softInputMode = target.info.softInputMode;
info.uiOptions = target.info.uiOptions;
+ info.parentActivityName = target.info.parentActivityName;
Activity a = new Activity(mParseActivityAliasArgs, info);
if (outError[0] != null) {
@@ -2295,6 +2309,20 @@ public class PackageParser {
a.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
+ String parentName = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_parentActivityName,
+ 0);
+ if (parentName != null) {
+ String parentClassName = buildClassName(a.info.packageName, parentName, outError);
+ if (outError[0] == null) {
+ a.info.parentActivityName = parentClassName;
+ } else {
+ Log.e(TAG, "Activity alias " + a.info.name +
+ " specified invalid parentActivityName " + parentName);
+ outError[0] = null;
+ }
+ }
+
sa.recycle();
if (outError[0] != null) {
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 3115eff..f1dffa1 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -85,6 +85,8 @@ public class ActionBarImpl extends ActionBar {
private TabImpl mSelectedTab;
private int mSavedTabPosition = INVALID_POSITION;
+ private boolean mDisplayHomeAsUpSet;
+
ActionModeImpl mActionMode;
ActionMode mDeferredDestroyActionMode;
ActionMode.Callback mDeferredModeDestroyCallback;
@@ -375,11 +377,17 @@ public class ActionBarImpl extends ActionBar {
}
public void setDisplayOptions(int options) {
+ if ((options & DISPLAY_HOME_AS_UP) != 0) {
+ mDisplayHomeAsUpSet = true;
+ }
mActionView.setDisplayOptions(options);
}
public void setDisplayOptions(int options, int mask) {
final int current = mActionView.getDisplayOptions();
+ if ((mask & DISPLAY_HOME_AS_UP) != 0) {
+ mDisplayHomeAsUpSet = true;
+ }
mActionView.setDisplayOptions((options & mask) | (current & ~mask));
}
@@ -1072,4 +1080,10 @@ public class ActionBarImpl extends ActionBar {
public void setLogo(Drawable logo) {
mActionView.setLogo(logo);
}
+
+ public void setDefaultDisplayHomeAsUpEnabled(boolean enable) {
+ if (!mDisplayHomeAsUpSet) {
+ setDisplayHomeAsUpEnabled(enable);
+ }
+ }
}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 1649c48..d414c7f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -740,6 +740,9 @@
<flag name="splitActionBarWhenNarrow" value="1" />
</attr>
+ <!-- The name of the logical parent of the activity as it appears in the manifest. -->
+ <attr name="parentActivityName" format="string" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1348,6 +1351,7 @@
<attr name="immersive" />
<attr name="hardwareAccelerated" />
<attr name="uiOptions" />
+ <attr name="parentActivityName" />
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
@@ -1384,6 +1388,7 @@
component specific values). -->
<attr name="enabled" />
<attr name="exported" />
+ <attr name="parentActivityName" />
</declare-styleable>
<!-- The <code>meta-data</code> tag is used to attach additional
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f010a02..72cb73b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3574,4 +3574,6 @@
<public type="attr" name="layout_marginStart"/>
<public type="attr" name="layout_marginEnd"/>
+ <public type="attr" name="parentActivityName" />
+
</resources>
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index a6fbdd7..33250b8 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -62,10 +62,10 @@ import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -75,12 +75,12 @@ import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -113,10 +113,10 @@ import android.os.UserId;
import android.provider.Settings;
import android.text.format.Time;
import android.util.EventLog;
-import android.util.Pair;
-import android.util.Slog;
import android.util.Log;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
@@ -140,7 +140,6 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.lang.IllegalStateException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
@@ -13281,6 +13280,102 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity) {
+ ActivityRecord srec = ActivityRecord.forToken(token);
+ return srec.task.affinity != null && srec.task.affinity.equals(destAffinity);
+ }
+
+ public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+ Intent resultData) {
+ ComponentName dest = destIntent.getComponent();
+
+ synchronized (this) {
+ ActivityRecord srec = ActivityRecord.forToken(token);
+ ArrayList<ActivityRecord> history = srec.stack.mHistory;
+ final int start = history.indexOf(srec);
+ if (start < 0) {
+ // Current activity is not in history stack; do nothing.
+ return false;
+ }
+ int finishTo = start - 1;
+ ActivityRecord parent = null;
+ boolean foundParentInTask = false;
+ if (dest != null) {
+ TaskRecord tr = srec.task;
+ for (int i = start - 1; i >= 0; i--) {
+ ActivityRecord r = history.get(i);
+ if (tr != r.task) {
+ // Couldn't find parent in the same task; stop at the one above this.
+ // (Root of current task; in-app "home" behavior)
+ // Always at least finish the current activity.
+ finishTo = Math.min(start - 1, i + 1);
+ parent = history.get(finishTo);
+ break;
+ } else if (r.info.packageName.equals(dest.getPackageName()) &&
+ r.info.name.equals(dest.getClassName())) {
+ finishTo = i;
+ parent = r;
+ foundParentInTask = true;
+ break;
+ }
+ }
+ }
+
+ if (mController != null) {
+ ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0);
+ if (next != null) {
+ // ask watcher if this is allowed
+ boolean resumeOK = true;
+ try {
+ resumeOK = mController.activityResuming(next.packageName);
+ } catch (RemoteException e) {
+ mController = null;
+ }
+
+ if (!resumeOK) {
+ return false;
+ }
+ }
+ }
+ final long origId = Binder.clearCallingIdentity();
+ for (int i = start; i > finishTo; i--) {
+ ActivityRecord r = history.get(i);
+ mMainStack.requestFinishActivityLocked(r.appToken, resultCode, resultData,
+ "navigate-up");
+ // Only return the supplied result for the first activity finished
+ resultCode = Activity.RESULT_CANCELED;
+ resultData = null;
+ }
+
+ if (parent != null && foundParentInTask) {
+ final int parentLaunchMode = parent.info.launchMode;
+ final int destIntentFlags = destIntent.getFlags();
+ if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE ||
+ parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK ||
+ parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP ||
+ (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+ parent.deliverNewIntentLocked(srec.app.uid, destIntent);
+ } else {
+ try {
+ ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
+ destIntent.getComponent(), 0, UserId.getCallingUserId());
+ int res = mMainStack.startActivityLocked(srec.app.thread, destIntent,
+ null, aInfo, parent.appToken, null,
+ 0, -1, parent.launchedFromUid, 0, null, true, null);
+ foundParentInTask = res == ActivityManager.START_SUCCESS;
+ } catch (RemoteException e) {
+ foundParentInTask = false;
+ }
+ mMainStack.requestFinishActivityLocked(parent.appToken, resultCode,
+ resultData, "navigate-up");
+ }
+ }
+ Binder.restoreCallingIdentity(origId);
+ return foundParentInTask;
+ }
+ }
+
// =========================================================
// LIFETIME MANAGEMENT
// =========================================================