Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd8fab26 authored by Adam Powell's avatar Adam Powell
Browse files

TaskStackBuilder and Activity navigation features for framework

Promote navigation helpers from the support library to the core
platform.

The support library's meta-data element has been replaced with a
first-class parentActivityName attribute. This attribute is valid
on both activity and activity-alias elements. An activity-alias
will inherit the target activity's parentActivityName if one is
not explicitly specified.

Automatic Up navigation for Activities

Add the public method onNavigateUp() to Activity. The default
implementation will use the metadata supplied in the manifest about an
activity's hierarchical parent (parentActivityName) to do the right
thing.

If any activities in the parent chain require special Intent
arguments, the Activity subclass should override onNavigateUp() to
properly implement Up navigation for the app, supplying such arguments
as needed.

If automatic Up navigation within the same task can't find an activity
matching the supplied intent in the current task stack, it will act as
an in-app "home" and return to the root activity (presumably the app's
front page) in that task. (From this state, pressing "back" with
default behavior will return to the launcher.)

Change-Id: If163e27e59587f7af36975a09c986cb117ec3bc6
parent d9966c4c
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -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;
+216 −2
Original line number Diff line number Diff line
@@ -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,9 +2654,104 @@ 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) {
+0 −1
Original line number Diff line number Diff line
@@ -1774,5 +1774,4 @@ public class ActivityManager {
            return false;
        }
    }

}
+67 −5
Original line number Diff line number Diff line
@@ -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;
}
+8 −0
Original line number Diff line number Diff line
@@ -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;
}
Loading