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