blob: 9c83362f608625dda0d949395476fd9d7cb94399 [file] [log] [blame]
Adam Powelldd8fab22012-03-22 17:47:27 -07001/*
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
17package android.app;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.ActivityInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
Adam Powellf78a8442012-05-01 18:09:32 -070025import android.os.Bundle;
Adam Powelldd8fab22012-03-22 17:47:27 -070026import android.util.Log;
27
28import java.util.ArrayList;
Adam Powelldd8fab22012-03-22 17:47:27 -070029
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 */
Adam Powellf78a8442012-05-01 18:09:32 -070059public class TaskStackBuilder {
Adam Powelldd8fab22012-03-22 17:47:27 -070060 private static final String TAG = "TaskStackBuilder";
61
62 private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
63 private final Context mSourceContext;
64
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 */
Adam Powellf78a8442012-05-01 18:09:32 -070076 public static TaskStackBuilder create(Context context) {
Adam Powelldd8fab22012-03-22 17:47:27 -070077 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 */
87 public TaskStackBuilder addNextIntent(Intent nextIntent) {
88 mIntents.add(nextIntent);
89 return this;
90 }
91
92 /**
Adam Powellf78a8442012-05-01 18:09:32 -070093 * 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 */
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 /**
Adam Powelldd8fab22012-03-22 17:47:27 -0700117 * Add the activity parent chain as specified by the
Adam Powell3c464bd2012-04-23 13:17:08 -0700118 * {@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.
Adam Powelldd8fab22012-03-22 17:47:27 -0700122 *
123 * @param sourceActivity All parents of this activity will be added
124 * @return This TaskStackBuilder for method chaining
125 */
126 public TaskStackBuilder addParentStack(Activity sourceActivity) {
Adam Powell5a4010c2012-09-16 15:14:05 -0700127 final Intent parent = sourceActivity.getParentActivityIntent();
128 if (parent != null) {
129 // We have the actual parent intent, build the rest from static metadata
130 // then add the direct parent intent to the end.
131 addParentStack(parent.getComponent());
132 addNextIntent(parent);
Adam Powelldd8fab22012-03-22 17:47:27 -0700133 }
134 return this;
135 }
136
137 /**
138 * Add the activity parent chain as specified by the
139 * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity
140 * (or activity-alias) element in the application's manifest to the task stack builder.
141 *
142 * @param sourceActivityClass All parents of this activity will be added
143 * @return This TaskStackBuilder for method chaining
144 */
145 public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
Adam Powell5a4010c2012-09-16 15:14:05 -0700146 return addParentStack(new ComponentName(mSourceContext, sourceActivityClass));
Adam Powelldd8fab22012-03-22 17:47:27 -0700147 }
148
149 /**
Adam Powell3c464bd2012-04-23 13:17:08 -0700150 * 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 sourceActivityName Must specify an Activity component. All parents of
155 * this activity will be added
156 * @return This TaskStackBuilder for method chaining
157 */
158 public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {
159 final int insertAt = mIntents.size();
160 PackageManager pm = mSourceContext.getPackageManager();
161 try {
162 ActivityInfo info = pm.getActivityInfo(sourceActivityName, 0);
163 String parentActivity = info.parentActivityName;
Adam Powell5c43ec92012-05-08 16:55:20 -0700164 while (parentActivity != null) {
Adam Powell5a4010c2012-09-16 15:14:05 -0700165 final ComponentName target = new ComponentName(mSourceContext, parentActivity);
166 info = pm.getActivityInfo(target, 0);
Adam Powell3c464bd2012-04-23 13:17:08 -0700167 parentActivity = info.parentActivityName;
Adam Powell5a4010c2012-09-16 15:14:05 -0700168 final Intent parent = parentActivity == null && insertAt == 0
169 ? Intent.makeMainActivity(target)
170 : new Intent().setComponent(target);
171 mIntents.add(insertAt, parent);
Adam Powell3c464bd2012-04-23 13:17:08 -0700172 }
173 } catch (NameNotFoundException e) {
174 Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
175 throw new IllegalArgumentException(e);
176 }
177 return this;
178 }
179
180 /**
Adam Powelldd8fab22012-03-22 17:47:27 -0700181 * @return the number of intents added so far.
182 */
183 public int getIntentCount() {
184 return mIntents.size();
185 }
186
187 /**
Adam Powellf78a8442012-05-01 18:09:32 -0700188 * Return the intent at the specified index for modification.
Adam Powelldd8fab22012-03-22 17:47:27 -0700189 * Useful if you need to modify the flags or extras of an intent that was previously added,
190 * for example with {@link #addParentStack(Activity)}.
191 *
192 * @param index Index from 0-getIntentCount()
193 * @return the intent at position index
194 */
Adam Powellf78a8442012-05-01 18:09:32 -0700195 public Intent editIntentAt(int index) {
Adam Powelldd8fab22012-03-22 17:47:27 -0700196 return mIntents.get(index);
197 }
198
Adam Powelldd8fab22012-03-22 17:47:27 -0700199 /**
200 * Start the task stack constructed by this builder.
201 */
202 public void startActivities() {
Adam Powellf78a8442012-05-01 18:09:32 -0700203 startActivities(null);
204 }
205
206 /**
207 * Start the task stack constructed by this builder.
208 *
209 * @param options Additional options for how the Activity should be started.
210 * See {@link android.content.Context#startActivity(Intent, Bundle)
211 * Context.startActivity(Intent, Bundle)} for more details.
212 */
213 public void startActivities(Bundle options) {
Adam Powelldd8fab22012-03-22 17:47:27 -0700214 if (mIntents.isEmpty()) {
215 throw new IllegalStateException(
216 "No intents added to TaskStackBuilder; cannot startActivities");
217 }
218
219 Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
220 intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
221 Intent.FLAG_ACTIVITY_CLEAR_TASK |
222 Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Adam Powellf78a8442012-05-01 18:09:32 -0700223 mSourceContext.startActivities(intents, options);
Adam Powelldd8fab22012-03-22 17:47:27 -0700224 }
225
226 /**
227 * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
228 *
229 * @param requestCode Private request code for the sender
230 * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
231 * {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
232 * {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
233 * {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
234 * intent that can be supplied when the actual send happens.
Adam Powellf78a8442012-05-01 18:09:32 -0700235 *
Adam Powelldd8fab22012-03-22 17:47:27 -0700236 * @return The obtained PendingIntent
237 */
238 public PendingIntent getPendingIntent(int requestCode, int flags) {
Adam Powellf78a8442012-05-01 18:09:32 -0700239 return getPendingIntent(requestCode, flags, null);
240 }
241
242 /**
243 * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
244 *
245 * @param requestCode Private request code for the sender
246 * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
247 * {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
248 * {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
249 * {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
250 * intent that can be supplied when the actual send happens.
251 * @param options Additional options for how the Activity should be started.
252 * See {@link android.content.Context#startActivity(Intent, Bundle)
253 * Context.startActivity(Intent, Bundle)} for more details.
254 *
255 * @return The obtained PendingIntent
256 */
257 public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
Adam Powell8ab700c2012-04-05 17:54:10 -0700258 if (mIntents.isEmpty()) {
259 throw new IllegalStateException(
260 "No intents added to TaskStackBuilder; cannot getPendingIntent");
261 }
262
Adam Powelldd8fab22012-03-22 17:47:27 -0700263 Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
Adam Powell8ab700c2012-04-05 17:54:10 -0700264 intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
265 Intent.FLAG_ACTIVITY_CLEAR_TASK |
266 Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Adam Powellf78a8442012-05-01 18:09:32 -0700267 return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags, options);
268 }
269
270 /**
271 * Return an array containing the intents added to this builder. The intent at the
272 * root of the task stack will appear as the first item in the array and the
273 * intent at the top of the stack will appear as the last item.
274 *
275 * @return An array containing the intents added to this builder.
276 */
277 public Intent[] getIntents() {
278 return mIntents.toArray(new Intent[mIntents.size()]);
Adam Powelldd8fab22012-03-22 17:47:27 -0700279 }
280}