• 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.support.v4.app;
18 
19 import android.support.annotation.RequiresApi;
20 import android.app.Activity;
21 import android.app.PendingIntent;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.os.Build;
27 import android.os.Bundle;
28 import android.support.v4.content.ContextCompat;
29 import android.util.Log;
30 
31 import java.util.ArrayList;
32 import java.util.Iterator;
33 
34 /**
35  * Utility class for constructing synthetic back stacks for cross-task navigation
36  * on Android 3.0 and newer.
37  *
38  * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
39  * app navigation using the back key changed. The back key's behavior is local
40  * to the current task and does not capture navigation across different tasks.
41  * Navigating across tasks and easily reaching the previous task is accomplished
42  * through the "recents" UI, accessible through the software-provided Recents key
43  * on the navigation or system bar. On devices with the older hardware button configuration
44  * the recents UI can be accessed with a long press on the Home key.</p>
45  *
46  * <p>When crossing from one task stack to another post-Android 3.0,
47  * the application should synthesize a back stack/history for the new task so that
48  * the user may navigate out of the new task and back to the Launcher by repeated
49  * presses of the back key. Back key presses should not navigate across task stacks.</p>
50  *
51  * <p>TaskStackBuilder provides a backward-compatible way to obey the correct conventions
52  * around cross-task navigation on the device's version of the platform. On devices running
53  * Android 3.0 or newer, calls to the {@link #startActivities()} method or sending the
54  * {@link PendingIntent} generated by {@link #getPendingIntent(int, int)} will construct
55  * the synthetic back stack as prescribed. On devices running older versions of the platform,
56  * these same calls will invoke the topmost activity in the supplied stack, ignoring
57  * the rest of the synthetic stack and allowing the back key to navigate back to the previous
58  * task.</p>
59  *
60  * <div class="special reference">
61  * <h3>About Navigation</h3>
62  * For more detailed information about tasks, the back stack, and navigation design guidelines,
63  * please read
64  * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a>
65  * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a>
66  * from the design guide.
67  * </div>
68  */
69 public final class TaskStackBuilder implements Iterable<Intent> {
70     private static final String TAG = "TaskStackBuilder";
71 
72     public interface SupportParentable {
getSupportParentActivityIntent()73         Intent getSupportParentActivityIntent();
74     }
75 
76     static class TaskStackBuilderBaseImpl {
getPendingIntent(Context context, Intent[] intents, int requestCode, int flags, Bundle options)77         public PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
78                 int flags, Bundle options) {
79             intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
80                     | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
81             return PendingIntent.getActivities(context, requestCode, intents, flags);
82         }
83     }
84 
85     @RequiresApi(16)
86     static class TaskStackBuilderApi16Impl extends TaskStackBuilderBaseImpl {
87         @Override
getPendingIntent(Context context, Intent[] intents, int requestCode, int flags, Bundle options)88         public PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
89                 int flags, Bundle options) {
90             intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
91                     | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
92             return PendingIntent.getActivities(context, requestCode, intents, flags, options);
93         }
94     }
95 
96     private static final TaskStackBuilderBaseImpl IMPL;
97 
98     static {
99         if (Build.VERSION.SDK_INT >= 16) {
100             IMPL = new TaskStackBuilderApi16Impl();
101         } else {
102             IMPL = new TaskStackBuilderBaseImpl();
103         }
104     }
105 
106     private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
107     private final Context mSourceContext;
108 
TaskStackBuilder(Context a)109     private TaskStackBuilder(Context a) {
110         mSourceContext = a;
111     }
112 
113     /**
114      * Return a new TaskStackBuilder for launching a fresh task stack consisting
115      * of a series of activities.
116      *
117      * @param context The context that will launch the new task stack or generate a PendingIntent
118      * @return A new TaskStackBuilder
119      */
create(Context context)120     public static TaskStackBuilder create(Context context) {
121         return new TaskStackBuilder(context);
122     }
123 
124     /**
125      * Return a new TaskStackBuilder for launching a fresh task stack consisting
126      * of a series of activities.
127      *
128      * @param context The context that will launch the new task stack or generate a PendingIntent
129      * @return A new TaskStackBuilder
130      *
131      * @deprecated use {@link #create(Context)} instead
132      */
133     @Deprecated
from(Context context)134     public static TaskStackBuilder from(Context context) {
135         return create(context);
136     }
137 
138     /**
139      * Add a new Intent to the task stack. The most recently added Intent will invoke
140      * the Activity at the top of the final task stack.
141      *
142      * @param nextIntent Intent for the next Activity in the synthesized task stack
143      * @return This TaskStackBuilder for method chaining
144      */
addNextIntent(Intent nextIntent)145     public TaskStackBuilder addNextIntent(Intent nextIntent) {
146         mIntents.add(nextIntent);
147         return this;
148     }
149 
150     /**
151      * Add a new Intent with the resolved chain of parents for the target activity to
152      * the task stack.
153      *
154      * <p>This is equivalent to calling {@link #addParentStack(ComponentName) addParentStack}
155      * with the resolved ComponentName of nextIntent (if it can be resolved), followed by
156      * {@link #addNextIntent(Intent) addNextIntent} with nextIntent.</p>
157      *
158      * @param nextIntent Intent for the topmost Activity in the synthesized task stack.
159      *                   Its chain of parents as specified in the manifest will be added.
160      * @return This TaskStackBuilder for method chaining.
161      */
addNextIntentWithParentStack(Intent nextIntent)162     public TaskStackBuilder addNextIntentWithParentStack(Intent nextIntent) {
163         ComponentName target = nextIntent.getComponent();
164         if (target == null) {
165             target = nextIntent.resolveActivity(mSourceContext.getPackageManager());
166         }
167         if (target != null) {
168             addParentStack(target);
169         }
170         addNextIntent(nextIntent);
171         return this;
172     }
173 
174     /**
175      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
176      * to the task stack builder.
177      *
178      * @param sourceActivity All parents of this activity will be added
179      * @return This TaskStackBuilder for method chaining
180      */
addParentStack(Activity sourceActivity)181     public TaskStackBuilder addParentStack(Activity sourceActivity) {
182         Intent parent = null;
183         if (sourceActivity instanceof SupportParentable) {
184             parent = ((SupportParentable) sourceActivity).getSupportParentActivityIntent();
185         }
186         if (parent == null) {
187             parent = NavUtils.getParentActivityIntent(sourceActivity);
188         }
189 
190         if (parent != null) {
191             // We have the actual parent intent, build the rest from static metadata
192             // then add the direct parent intent to the end.
193             ComponentName target = parent.getComponent();
194             if (target == null) {
195                 target = parent.resolveActivity(mSourceContext.getPackageManager());
196             }
197             addParentStack(target);
198             addNextIntent(parent);
199         }
200         return this;
201     }
202 
203     /**
204      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
205      * to the task stack builder.
206      *
207      * @param sourceActivityClass All parents of this activity will be added
208      * @return This TaskStackBuilder for method chaining
209      */
addParentStack(Class<?> sourceActivityClass)210     public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
211         return addParentStack(new ComponentName(mSourceContext, sourceActivityClass));
212     }
213 
214     /**
215      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
216      * to the task stack builder.
217      *
218      * @param sourceActivityName Must specify an Activity component. All parents of
219      *                           this activity will be added
220      * @return This TaskStackBuilder for method chaining
221      */
addParentStack(ComponentName sourceActivityName)222     public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {
223         final int insertAt = mIntents.size();
224         try {
225             Intent parent = NavUtils.getParentActivityIntent(mSourceContext, sourceActivityName);
226             while (parent != null) {
227                 mIntents.add(insertAt, parent);
228                 parent = NavUtils.getParentActivityIntent(mSourceContext, parent.getComponent());
229             }
230         } catch (NameNotFoundException e) {
231             Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
232             throw new IllegalArgumentException(e);
233         }
234         return this;
235     }
236 
237     /**
238      * @return the number of intents added so far.
239      */
getIntentCount()240     public int getIntentCount() {
241         return mIntents.size();
242     }
243 
244     /**
245      * Get the intent at the specified index.
246      * Useful if you need to modify the flags or extras of an intent that was previously added,
247      * for example with {@link #addParentStack(Activity)}.
248      *
249      * @param index Index from 0-getIntentCount()
250      * @return the intent at position index
251      *
252      * @deprecated Renamed to editIntentAt to better reflect intended usage
253      */
254     @Deprecated
getIntent(int index)255     public Intent getIntent(int index) {
256         return editIntentAt(index);
257     }
258 
259     /**
260      * Return the intent at the specified index for modification.
261      * Useful if you need to modify the flags or extras of an intent that was previously added,
262      * for example with {@link #addParentStack(Activity)}.
263      *
264      * @param index Index from 0-getIntentCount()
265      * @return the intent at position index
266      */
editIntentAt(int index)267     public Intent editIntentAt(int index) {
268         return mIntents.get(index);
269     }
270 
271     /**
272      * @deprecated Use editIntentAt instead
273      */
274     @Override
275     @Deprecated
iterator()276     public Iterator<Intent> iterator() {
277         return mIntents.iterator();
278     }
279 
280     /**
281      * Start the task stack constructed by this builder. The Context used to obtain
282      * this builder must be an Activity.
283      *
284      * <p>On devices that do not support API level 11 or higher the topmost activity
285      * will be started as a new task. On devices that do support API level 11 or higher
286      * the new task stack will be created in its entirety.</p>
287      */
startActivities()288     public void startActivities() {
289         startActivities(null);
290     }
291 
292     /**
293      * Start the task stack constructed by this builder. The Context used to obtain
294      * this builder must be an Activity.
295      *
296      * <p>On devices that do not support API level 11 or higher the topmost activity
297      * will be started as a new task. On devices that do support API level 11 or higher
298      * the new task stack will be created in its entirety.</p>
299      *
300      * @param options Additional options for how the Activity should be started.
301      * See {@link android.content.Context#startActivity(Intent, Bundle)}
302      */
startActivities(Bundle options)303     public void startActivities(Bundle options) {
304         if (mIntents.isEmpty()) {
305             throw new IllegalStateException(
306                     "No intents added to TaskStackBuilder; cannot startActivities");
307         }
308 
309         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
310         intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
311                 | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
312         if (!ContextCompat.startActivities(mSourceContext, intents, options)) {
313             Intent topIntent = new Intent(intents[intents.length - 1]);
314             topIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
315             mSourceContext.startActivity(topIntent);
316         }
317     }
318 
319     /**
320      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
321      *
322      * @param requestCode Private request code for the sender
323      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
324      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
325      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
326      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
327      *              intent that can be supplied when the actual send happens.
328      * @return The obtained PendingIntent
329      */
getPendingIntent(int requestCode, int flags)330     public PendingIntent getPendingIntent(int requestCode, int flags) {
331         return getPendingIntent(requestCode, flags, null);
332     }
333 
334     /**
335      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
336      *
337      * @param requestCode Private request code for the sender
338      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
339      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
340      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
341      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
342      *              intent that can be supplied when the actual send happens.
343      * @param options Additional options for how the Activity should be started.
344      * See {@link android.content.Context#startActivity(Intent, Bundle)}
345      * @return The obtained PendingIntent
346      */
getPendingIntent(int requestCode, int flags, Bundle options)347     public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
348         if (mIntents.isEmpty()) {
349             throw new IllegalStateException(
350                     "No intents added to TaskStackBuilder; cannot getPendingIntent");
351         }
352 
353         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
354         intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
355                 | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
356         // Appropriate flags will be added by the call below.
357         return IMPL.getPendingIntent(mSourceContext, intents, requestCode, flags, options);
358     }
359 
360     /**
361      * Return an array containing the intents added to this builder. The intent at the
362      * root of the task stack will appear as the first item in the array and the
363      * intent at the top of the stack will appear as the last item.
364      *
365      * @return An array containing the intents added to this builder.
366      */
getIntents()367     public Intent[] getIntents() {
368         Intent[] intents = new Intent[mIntents.size()];
369         if (intents.length == 0) return intents;
370 
371         intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
372                 | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
373         for (int i = 1; i < intents.length; i++) {
374             intents[i] = new Intent(mIntents.get(i));
375         }
376         return intents;
377     }
378 }
379