• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ActivityInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.os.Bundle;
26 import android.util.Log;
27 
28 import java.util.ArrayList;
29 
30 /**
31  * Utility class for constructing synthetic back stacks for cross-task navigation
32  * on Android 3.0 and newer.
33  *
34  * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
35  * app navigation using the back key changed. The back key's behavior is local
36  * to the current task and does not capture navigation across different tasks.
37  * Navigating across tasks and easily reaching the previous task is accomplished
38  * through the "recents" UI, accessible through the software-provided Recents key
39  * on the navigation or system bar. On devices with the older hardware button configuration
40  * the recents UI can be accessed with a long press on the Home key.</p>
41  *
42  * <p>When crossing from one task stack to another post-Android 3.0,
43  * the application should synthesize a back stack/history for the new task so that
44  * the user may navigate out of the new task and back to the Launcher by repeated
45  * presses of the back key. Back key presses should not navigate across task stacks.</p>
46  *
47  * <p>TaskStackBuilder provides a way to obey the correct conventions
48  * around cross-task navigation.</p>
49  *
50  * <div class="special reference">
51  * <h3>About Navigation</h3>
52  * For more detailed information about tasks, the back stack, and navigation design guidelines,
53  * please read
54  * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a>
55  * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a>
56  * from the design guide.
57  * </div>
58  */
59 public class TaskStackBuilder {
60     private static final String TAG = "TaskStackBuilder";
61 
62     private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
63     private final Context mSourceContext;
64 
TaskStackBuilder(Context a)65     private TaskStackBuilder(Context a) {
66         mSourceContext = a;
67     }
68 
69     /**
70      * Return a new TaskStackBuilder for launching a fresh task stack consisting
71      * of a series of activities.
72      *
73      * @param context The context that will launch the new task stack or generate a PendingIntent
74      * @return A new TaskStackBuilder
75      */
create(Context context)76     public static TaskStackBuilder create(Context context) {
77         return new TaskStackBuilder(context);
78     }
79 
80     /**
81      * Add a new Intent to the task stack. The most recently added Intent will invoke
82      * the Activity at the top of the final task stack.
83      *
84      * @param nextIntent Intent for the next Activity in the synthesized task stack
85      * @return This TaskStackBuilder for method chaining
86      */
addNextIntent(Intent nextIntent)87     public TaskStackBuilder addNextIntent(Intent nextIntent) {
88         mIntents.add(nextIntent);
89         return this;
90     }
91 
92     /**
93      * Add a new Intent with the resolved chain of parents for the target activity to
94      * the task stack.
95      *
96      * <p>This is equivalent to calling {@link #addParentStack(ComponentName) addParentStack}
97      * with the resolved ComponentName of nextIntent (if it can be resolved), followed by
98      * {@link #addNextIntent(Intent) addNextIntent} with nextIntent.</p>
99      *
100      * @param nextIntent Intent for the topmost Activity in the synthesized task stack.
101      *                   Its chain of parents as specified in the manifest will be added.
102      * @return This TaskStackBuilder for method chaining.
103      */
addNextIntentWithParentStack(Intent nextIntent)104     public TaskStackBuilder addNextIntentWithParentStack(Intent nextIntent) {
105         ComponentName target = nextIntent.getComponent();
106         if (target == null) {
107             target = nextIntent.resolveActivity(mSourceContext.getPackageManager());
108         }
109         if (target != null) {
110             addParentStack(target);
111         }
112         addNextIntent(nextIntent);
113         return this;
114     }
115 
116     /**
117      * Add the activity parent chain as specified by the
118      * {@link Activity#getParentActivityIntent() getParentActivityIntent()} method of the activity
119      * specified and the {@link android.R.attr#parentActivityName parentActivityName} attributes
120      * of each successive activity (or activity-alias) element in the application's manifest
121      * to the task stack builder.
122      *
123      * @param sourceActivity All parents of this activity will be added
124      * @return This TaskStackBuilder for method chaining
125      */
addParentStack(Activity sourceActivity)126     public TaskStackBuilder addParentStack(Activity sourceActivity) {
127         final int insertAt = mIntents.size();
128         Intent parent = sourceActivity.getParentActivityIntent();
129         PackageManager pm = sourceActivity.getPackageManager();
130         while (parent != null) {
131             mIntents.add(insertAt, parent);
132             try {
133                 ActivityInfo info = pm.getActivityInfo(parent.getComponent(), 0);
134                 String parentActivity = info.parentActivityName;
135                 if (parentActivity != null) {
136                     parent = new Intent().setComponent(
137                             new ComponentName(mSourceContext, parentActivity));
138                 } else {
139                     parent = null;
140                 }
141             } catch (NameNotFoundException e) {
142                 Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
143                 throw new IllegalArgumentException(e);
144             }
145         }
146         return this;
147     }
148 
149     /**
150      * Add the activity parent chain as specified by the
151      * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity
152      * (or activity-alias) element in the application's manifest to the task stack builder.
153      *
154      * @param sourceActivityClass All parents of this activity will be added
155      * @return This TaskStackBuilder for method chaining
156      */
addParentStack(Class<?> sourceActivityClass)157     public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
158         final int insertAt = mIntents.size();
159         PackageManager pm = mSourceContext.getPackageManager();
160         try {
161             ActivityInfo info = pm.getActivityInfo(
162                     new ComponentName(mSourceContext, sourceActivityClass), 0);
163             String parentActivity = info.parentActivityName;
164             while (parentActivity != null) {
165                 Intent parent = new Intent().setComponent(
166                         new ComponentName(mSourceContext, parentActivity));
167                 mIntents.add(insertAt, parent);
168                 info = pm.getActivityInfo(parent.getComponent(), 0);
169                 parentActivity = info.parentActivityName;
170             }
171         } catch (NameNotFoundException e) {
172             Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
173             throw new IllegalArgumentException(e);
174         }
175         return this;
176     }
177 
178     /**
179      * Add the activity parent chain as specified by the
180      * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity
181      * (or activity-alias) element in the application's manifest to the task stack builder.
182      *
183      * @param sourceActivityName Must specify an Activity component. All parents of
184      *                           this activity will be added
185      * @return This TaskStackBuilder for method chaining
186      */
addParentStack(ComponentName sourceActivityName)187     public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {
188         final int insertAt = mIntents.size();
189         PackageManager pm = mSourceContext.getPackageManager();
190         try {
191             ActivityInfo info = pm.getActivityInfo(sourceActivityName, 0);
192             String parentActivity = info.parentActivityName;
193             while (parentActivity != null) {
194                 Intent parent = new Intent().setComponent(
195                         new ComponentName(info.packageName, parentActivity));
196                 mIntents.add(insertAt, parent);
197                 info = pm.getActivityInfo(parent.getComponent(), 0);
198                 parentActivity = info.parentActivityName;
199             }
200         } catch (NameNotFoundException e) {
201             Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
202             throw new IllegalArgumentException(e);
203         }
204         return this;
205     }
206 
207     /**
208      * @return the number of intents added so far.
209      */
getIntentCount()210     public int getIntentCount() {
211         return mIntents.size();
212     }
213 
214     /**
215      * Return the intent at the specified index for modification.
216      * Useful if you need to modify the flags or extras of an intent that was previously added,
217      * for example with {@link #addParentStack(Activity)}.
218      *
219      * @param index Index from 0-getIntentCount()
220      * @return the intent at position index
221      */
editIntentAt(int index)222     public Intent editIntentAt(int index) {
223         return mIntents.get(index);
224     }
225 
226     /**
227      * Start the task stack constructed by this builder.
228      */
startActivities()229     public void startActivities() {
230         startActivities(null);
231     }
232 
233     /**
234      * Start the task stack constructed by this builder.
235      *
236      * @param options Additional options for how the Activity should be started.
237      * See {@link android.content.Context#startActivity(Intent, Bundle)
238      * Context.startActivity(Intent, Bundle)} for more details.
239      */
startActivities(Bundle options)240     public void startActivities(Bundle options) {
241         if (mIntents.isEmpty()) {
242             throw new IllegalStateException(
243                     "No intents added to TaskStackBuilder; cannot startActivities");
244         }
245 
246         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
247         intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
248                 Intent.FLAG_ACTIVITY_CLEAR_TASK |
249                 Intent.FLAG_ACTIVITY_TASK_ON_HOME);
250         mSourceContext.startActivities(intents, options);
251     }
252 
253     /**
254      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
255      *
256      * @param requestCode Private request code for the sender
257      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
258      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
259      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
260      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
261      *              intent that can be supplied when the actual send happens.
262      *
263      * @return The obtained PendingIntent
264      */
getPendingIntent(int requestCode, int flags)265     public PendingIntent getPendingIntent(int requestCode, int flags) {
266         return getPendingIntent(requestCode, flags, null);
267     }
268 
269     /**
270      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
271      *
272      * @param requestCode Private request code for the sender
273      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
274      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
275      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
276      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
277      *              intent that can be supplied when the actual send happens.
278      * @param options Additional options for how the Activity should be started.
279      * See {@link android.content.Context#startActivity(Intent, Bundle)
280      * Context.startActivity(Intent, Bundle)} for more details.
281      *
282      * @return The obtained PendingIntent
283      */
getPendingIntent(int requestCode, int flags, Bundle options)284     public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
285         if (mIntents.isEmpty()) {
286             throw new IllegalStateException(
287                     "No intents added to TaskStackBuilder; cannot getPendingIntent");
288         }
289 
290         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
291         intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
292                 Intent.FLAG_ACTIVITY_CLEAR_TASK |
293                 Intent.FLAG_ACTIVITY_TASK_ON_HOME);
294         return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags, options);
295     }
296 
297     /**
298      * Return an array containing the intents added to this builder. The intent at the
299      * root of the task stack will appear as the first item in the array and the
300      * intent at the top of the stack will appear as the last item.
301      *
302      * @return An array containing the intents added to this builder.
303      */
getIntents()304     public Intent[] getIntents() {
305         return mIntents.toArray(new Intent[mIntents.size()]);
306     }
307 }
308