diff options
-rw-r--r-- | api/current.txt | 22 | ||||
-rw-r--r-- | core/java/android/app/Activity.java | 218 | ||||
-rw-r--r-- | core/java/android/app/ActivityManager.java | 1 | ||||
-rw-r--r-- | core/java/android/app/ActivityManagerNative.java | 72 | ||||
-rw-r--r-- | core/java/android/app/IActivityManager.java | 8 | ||||
-rw-r--r-- | core/java/android/app/TaskStackBuilder.java | 212 | ||||
-rw-r--r-- | core/java/android/content/pm/ActivityInfo.java | 8 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageParser.java | 28 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ActionBarImpl.java | 14 | ||||
-rw-r--r-- | core/res/res/values/attrs_manifest.xml | 5 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 2 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActivityManagerService.java | 107 |
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 // ========================================================= |