• 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.app.Activity;
20 import android.app.PendingIntent;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.support.v4.content.ContextCompat;
28 import android.support.v4.content.IntentCompat;
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     interface TaskStackBuilderImpl {
getPendingIntent(Context context, Intent[] intents, int requestCode, int flags, Bundle options)77         PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
78                 int flags, Bundle options);
79     }
80 
81     static class TaskStackBuilderImplBase implements TaskStackBuilderImpl {
getPendingIntent(Context context, Intent[] intents, int requestCode, int flags, Bundle options)82         public PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
83                 int flags, Bundle options) {
84             Intent topIntent = new Intent(intents[intents.length - 1]);
85             topIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
86             return PendingIntent.getActivity(context, requestCode, topIntent, flags);
87         }
88     }
89 
90     static class TaskStackBuilderImplHoneycomb implements TaskStackBuilderImpl {
getPendingIntent(Context context, Intent[] intents, int requestCode, int flags, Bundle options)91         public PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
92                 int flags, Bundle options) {
93             intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
94                     IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
95                     IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
96             return TaskStackBuilderHoneycomb.getActivitiesPendingIntent(context, requestCode,
97                     intents, flags);
98         }
99     }
100 
101     static class TaskStackBuilderImplJellybean implements TaskStackBuilderImpl {
getPendingIntent(Context context, Intent[] intents, int requestCode, int flags, Bundle options)102         public PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
103                 int flags, Bundle options) {
104             intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
105                     IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
106                     IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
107             return TaskStackBuilderJellybean.getActivitiesPendingIntent(context, requestCode,
108                     intents, flags, options);
109         }
110     }
111 
112     private static final TaskStackBuilderImpl IMPL;
113 
114     static {
115         if (Build.VERSION.SDK_INT >= 11) {
116             IMPL = new TaskStackBuilderImplHoneycomb();
117         } else {
118             IMPL = new TaskStackBuilderImplBase();
119         }
120     }
121 
122     private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
123     private final Context mSourceContext;
124 
TaskStackBuilder(Context a)125     private TaskStackBuilder(Context a) {
126         mSourceContext = a;
127     }
128 
129     /**
130      * Return a new TaskStackBuilder for launching a fresh task stack consisting
131      * of a series of activities.
132      *
133      * @param context The context that will launch the new task stack or generate a PendingIntent
134      * @return A new TaskStackBuilder
135      */
create(Context context)136     public static TaskStackBuilder create(Context context) {
137         return new TaskStackBuilder(context);
138     }
139 
140     /**
141      * Return a new TaskStackBuilder for launching a fresh task stack consisting
142      * of a series of activities.
143      *
144      * @param context The context that will launch the new task stack or generate a PendingIntent
145      * @return A new TaskStackBuilder
146      *
147      * @deprecated use {@link #create(Context)} instead
148      */
149     @Deprecated
from(Context context)150     public static TaskStackBuilder from(Context context) {
151         return create(context);
152     }
153 
154     /**
155      * Add a new Intent to the task stack. The most recently added Intent will invoke
156      * the Activity at the top of the final task stack.
157      *
158      * @param nextIntent Intent for the next Activity in the synthesized task stack
159      * @return This TaskStackBuilder for method chaining
160      */
addNextIntent(Intent nextIntent)161     public TaskStackBuilder addNextIntent(Intent nextIntent) {
162         mIntents.add(nextIntent);
163         return this;
164     }
165 
166     /**
167      * Add a new Intent with the resolved chain of parents for the target activity to
168      * the task stack.
169      *
170      * <p>This is equivalent to calling {@link #addParentStack(ComponentName) addParentStack}
171      * with the resolved ComponentName of nextIntent (if it can be resolved), followed by
172      * {@link #addNextIntent(Intent) addNextIntent} with nextIntent.</p>
173      *
174      * @param nextIntent Intent for the topmost Activity in the synthesized task stack.
175      *                   Its chain of parents as specified in the manifest will be added.
176      * @return This TaskStackBuilder for method chaining.
177      */
addNextIntentWithParentStack(Intent nextIntent)178     public TaskStackBuilder addNextIntentWithParentStack(Intent nextIntent) {
179         ComponentName target = nextIntent.getComponent();
180         if (target == null) {
181             target = nextIntent.resolveActivity(mSourceContext.getPackageManager());
182         }
183         if (target != null) {
184             addParentStack(target);
185         }
186         addNextIntent(nextIntent);
187         return this;
188     }
189 
190     /**
191      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
192      * to the task stack builder.
193      *
194      * @param sourceActivity All parents of this activity will be added
195      * @return This TaskStackBuilder for method chaining
196      */
addParentStack(Activity sourceActivity)197     public TaskStackBuilder addParentStack(Activity sourceActivity) {
198         Intent parent = null;
199         if (sourceActivity instanceof SupportParentable) {
200             parent = ((SupportParentable) sourceActivity).getSupportParentActivityIntent();
201         }
202         if (parent == null) {
203             parent = NavUtils.getParentActivityIntent(sourceActivity);
204         }
205 
206         if (parent != null) {
207             // We have the actual parent intent, build the rest from static metadata
208             // then add the direct parent intent to the end.
209             ComponentName target = parent.getComponent();
210             if (target == null) {
211                 target = parent.resolveActivity(mSourceContext.getPackageManager());
212             }
213             addParentStack(target);
214             addNextIntent(parent);
215         }
216         return this;
217     }
218 
219     /**
220      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
221      * to the task stack builder.
222      *
223      * @param sourceActivityClass All parents of this activity will be added
224      * @return This TaskStackBuilder for method chaining
225      */
addParentStack(Class<?> sourceActivityClass)226     public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
227         return addParentStack(new ComponentName(mSourceContext, sourceActivityClass));
228     }
229 
230     /**
231      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
232      * to the task stack builder.
233      *
234      * @param sourceActivityName Must specify an Activity component. All parents of
235      *                           this activity will be added
236      * @return This TaskStackBuilder for method chaining
237      */
addParentStack(ComponentName sourceActivityName)238     public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {
239         final int insertAt = mIntents.size();
240         try {
241             Intent parent = NavUtils.getParentActivityIntent(mSourceContext, sourceActivityName);
242             while (parent != null) {
243                 mIntents.add(insertAt, parent);
244                 parent = NavUtils.getParentActivityIntent(mSourceContext, parent.getComponent());
245             }
246         } catch (NameNotFoundException e) {
247             Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
248             throw new IllegalArgumentException(e);
249         }
250         return this;
251     }
252 
253     /**
254      * @return the number of intents added so far.
255      */
getIntentCount()256     public int getIntentCount() {
257         return mIntents.size();
258     }
259 
260     /**
261      * Get the intent at the specified index.
262      * Useful if you need to modify the flags or extras of an intent that was previously added,
263      * for example with {@link #addParentStack(Activity)}.
264      *
265      * @param index Index from 0-getIntentCount()
266      * @return the intent at position index
267      *
268      * @deprecated Renamed to editIntentAt to better reflect intended usage
269      */
270     @Deprecated
getIntent(int index)271     public Intent getIntent(int index) {
272         return editIntentAt(index);
273     }
274 
275     /**
276      * Return the intent at the specified index for modification.
277      * Useful if you need to modify the flags or extras of an intent that was previously added,
278      * for example with {@link #addParentStack(Activity)}.
279      *
280      * @param index Index from 0-getIntentCount()
281      * @return the intent at position index
282      */
editIntentAt(int index)283     public Intent editIntentAt(int index) {
284         return mIntents.get(index);
285     }
286 
287     /**
288      * @deprecated Use editIntentAt instead
289      */
290     @Deprecated
iterator()291     public Iterator<Intent> iterator() {
292         return mIntents.iterator();
293     }
294 
295     /**
296      * Start the task stack constructed by this builder. The Context used to obtain
297      * this builder must be an Activity.
298      *
299      * <p>On devices that do not support API level 11 or higher the topmost activity
300      * will be started as a new task. On devices that do support API level 11 or higher
301      * the new task stack will be created in its entirety.</p>
302      */
startActivities()303     public void startActivities() {
304         startActivities(null);
305     }
306 
307     /**
308      * Start the task stack constructed by this builder. The Context used to obtain
309      * this builder must be an Activity.
310      *
311      * <p>On devices that do not support API level 11 or higher the topmost activity
312      * will be started as a new task. On devices that do support API level 11 or higher
313      * the new task stack will be created in its entirety.</p>
314      *
315      * @param options Additional options for how the Activity should be started.
316      * See {@link android.content.Context#startActivity(Intent, Bundle)
317      */
startActivities(Bundle options)318     public void startActivities(Bundle options) {
319         if (mIntents.isEmpty()) {
320             throw new IllegalStateException(
321                     "No intents added to TaskStackBuilder; cannot startActivities");
322         }
323 
324         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
325         intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
326                 IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
327                 IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
328         if (!ContextCompat.startActivities(mSourceContext, intents, options)) {
329             Intent topIntent = new Intent(intents[intents.length - 1]);
330             topIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
331             mSourceContext.startActivity(topIntent);
332         }
333     }
334 
335     /**
336      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
337      *
338      * @param requestCode Private request code for the sender
339      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
340      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
341      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
342      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
343      *              intent that can be supplied when the actual send happens.
344      * @return The obtained PendingIntent
345      */
getPendingIntent(int requestCode, int flags)346     public PendingIntent getPendingIntent(int requestCode, int flags) {
347         return getPendingIntent(requestCode, flags, null);
348     }
349 
350     /**
351      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
352      *
353      * @param requestCode Private request code for the sender
354      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
355      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
356      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
357      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
358      *              intent that can be supplied when the actual send happens.
359      * @param options Additional options for how the Activity should be started.
360      * See {@link android.content.Context#startActivity(Intent, Bundle)
361      * @return The obtained PendingIntent
362      */
getPendingIntent(int requestCode, int flags, Bundle options)363     public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
364         if (mIntents.isEmpty()) {
365             throw new IllegalStateException(
366                     "No intents added to TaskStackBuilder; cannot getPendingIntent");
367         }
368 
369         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
370         intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
371                 IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
372                 IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
373         // Appropriate flags will be added by the call below.
374         return IMPL.getPendingIntent(mSourceContext, intents, requestCode, flags, options);
375     }
376 
377     /**
378      * Return an array containing the intents added to this builder. The intent at the
379      * root of the task stack will appear as the first item in the array and the
380      * intent at the top of the stack will appear as the last item.
381      *
382      * @return An array containing the intents added to this builder.
383      */
getIntents()384     public Intent[] getIntents() {
385         Intent[] intents = new Intent[mIntents.size()];
386         if (intents.length == 0) return intents;
387 
388         intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
389                 IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
390                 IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
391         for (int i = 1; i < intents.length; i++) {
392             intents[i] = new Intent(mIntents.get(i));
393         }
394         return intents;
395     }
396 }
397