Home | History | Annotate | Download | only in app
      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 class TaskStackBuilder implements Iterable<Intent> {
     70     private static final String TAG = "TaskStackBuilder";
     71 
     72     public interface SupportParentable {
     73         Intent getSupportParentActivityIntent();
     74     }
     75 
     76     interface TaskStackBuilderImpl {
     77         PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
     78                 int flags, Bundle options);
     79     }
     80 
     81     static class TaskStackBuilderImplBase implements TaskStackBuilderImpl {
     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 {
     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 {
    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 
    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      */
    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     public static TaskStackBuilder from(Context context) {
    150         return create(context);
    151     }
    152 
    153     /**
    154      * Add a new Intent to the task stack. The most recently added Intent will invoke
    155      * the Activity at the top of the final task stack.
    156      *
    157      * @param nextIntent Intent for the next Activity in the synthesized task stack
    158      * @return This TaskStackBuilder for method chaining
    159      */
    160     public TaskStackBuilder addNextIntent(Intent nextIntent) {
    161         mIntents.add(nextIntent);
    162         return this;
    163     }
    164 
    165     /**
    166      * Add a new Intent with the resolved chain of parents for the target activity to
    167      * the task stack.
    168      *
    169      * <p>This is equivalent to calling {@link #addParentStack(ComponentName) addParentStack}
    170      * with the resolved ComponentName of nextIntent (if it can be resolved), followed by
    171      * {@link #addNextIntent(Intent) addNextIntent} with nextIntent.</p>
    172      *
    173      * @param nextIntent Intent for the topmost Activity in the synthesized task stack.
    174      *                   Its chain of parents as specified in the manifest will be added.
    175      * @return This TaskStackBuilder for method chaining.
    176      */
    177     public TaskStackBuilder addNextIntentWithParentStack(Intent nextIntent) {
    178         ComponentName target = nextIntent.getComponent();
    179         if (target == null) {
    180             target = nextIntent.resolveActivity(mSourceContext.getPackageManager());
    181         }
    182         if (target != null) {
    183             addParentStack(target);
    184         }
    185         addNextIntent(nextIntent);
    186         return this;
    187     }
    188 
    189     /**
    190      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
    191      * to the task stack builder.
    192      *
    193      * @param sourceActivity All parents of this activity will be added
    194      * @return This TaskStackBuilder for method chaining
    195      */
    196     public TaskStackBuilder addParentStack(Activity sourceActivity) {
    197         Intent parent = null;
    198         if (sourceActivity instanceof SupportParentable) {
    199             parent = ((SupportParentable) sourceActivity).getSupportParentActivityIntent();
    200         }
    201         if (parent == null) {
    202             parent = NavUtils.getParentActivityIntent(sourceActivity);
    203         }
    204 
    205         if (parent != null) {
    206             // We have the actual parent intent, build the rest from static metadata
    207             // then add the direct parent intent to the end.
    208             ComponentName target = parent.getComponent();
    209             if (target == null) {
    210                 target = parent.resolveActivity(mSourceContext.getPackageManager());
    211             }
    212             addParentStack(target);
    213             addNextIntent(parent);
    214         }
    215         return this;
    216     }
    217 
    218     /**
    219      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
    220      * to the task stack builder.
    221      *
    222      * @param sourceActivityClass All parents of this activity will be added
    223      * @return This TaskStackBuilder for method chaining
    224      */
    225     public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
    226         return addParentStack(new ComponentName(mSourceContext, sourceActivityClass));
    227     }
    228 
    229     /**
    230      * Add the activity parent chain as specified by manifest &lt;meta-data&gt; elements
    231      * to the task stack builder.
    232      *
    233      * @param sourceActivityName Must specify an Activity component. All parents of
    234      *                           this activity will be added
    235      * @return This TaskStackBuilder for method chaining
    236      */
    237     public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {
    238         final int insertAt = mIntents.size();
    239         try {
    240             Intent parent = NavUtils.getParentActivityIntent(mSourceContext, sourceActivityName);
    241             while (parent != null) {
    242                 mIntents.add(insertAt, parent);
    243                 parent = NavUtils.getParentActivityIntent(mSourceContext, parent.getComponent());
    244             }
    245         } catch (NameNotFoundException e) {
    246             Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
    247             throw new IllegalArgumentException(e);
    248         }
    249         return this;
    250     }
    251 
    252     /**
    253      * @return the number of intents added so far.
    254      */
    255     public int getIntentCount() {
    256         return mIntents.size();
    257     }
    258 
    259     /**
    260      * Get the intent at the specified index.
    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      *
    267      * @deprecated Renamed to editIntentAt to better reflect intended usage
    268      */
    269     public Intent getIntent(int index) {
    270         return editIntentAt(index);
    271     }
    272 
    273     /**
    274      * Return the intent at the specified index for modification.
    275      * Useful if you need to modify the flags or extras of an intent that was previously added,
    276      * for example with {@link #addParentStack(Activity)}.
    277      *
    278      * @param index Index from 0-getIntentCount()
    279      * @return the intent at position index
    280      */
    281     public Intent editIntentAt(int index) {
    282         return mIntents.get(index);
    283     }
    284 
    285     /**
    286      * @deprecated Use editIntentAt instead
    287      */
    288     public Iterator<Intent> iterator() {
    289         return mIntents.iterator();
    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     public void startActivities() {
    301         startActivities(null);
    302     }
    303 
    304     /**
    305      * Start the task stack constructed by this builder. The Context used to obtain
    306      * this builder must be an Activity.
    307      *
    308      * <p>On devices that do not support API level 11 or higher the topmost activity
    309      * will be started as a new task. On devices that do support API level 11 or higher
    310      * the new task stack will be created in its entirety.</p>
    311      *
    312      * @param options Additional options for how the Activity should be started.
    313      * See {@link android.content.Context#startActivity(Intent, Bundle)
    314      */
    315     public void startActivities(Bundle options) {
    316         if (mIntents.isEmpty()) {
    317             throw new IllegalStateException(
    318                     "No intents added to TaskStackBuilder; cannot startActivities");
    319         }
    320 
    321         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
    322         intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    323                 IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
    324                 IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
    325         if (!ContextCompat.startActivities(mSourceContext, intents, options)) {
    326             Intent topIntent = new Intent(intents[intents.length - 1]);
    327             topIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    328             mSourceContext.startActivity(topIntent);
    329         }
    330     }
    331 
    332     /**
    333      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
    334      *
    335      * @param requestCode Private request code for the sender
    336      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
    337      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
    338      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
    339      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
    340      *              intent that can be supplied when the actual send happens.
    341      * @return The obtained PendingIntent
    342      */
    343     public PendingIntent getPendingIntent(int requestCode, int flags) {
    344         return getPendingIntent(requestCode, flags, null);
    345     }
    346 
    347     /**
    348      * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far.
    349      *
    350      * @param requestCode Private request code for the sender
    351      * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT},
    352      *              {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT},
    353      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
    354      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
    355      *              intent that can be supplied when the actual send happens.
    356      * @param options Additional options for how the Activity should be started.
    357      * See {@link android.content.Context#startActivity(Intent, Bundle)
    358      * @return The obtained PendingIntent
    359      */
    360     public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
    361         if (mIntents.isEmpty()) {
    362             throw new IllegalStateException(
    363                     "No intents added to TaskStackBuilder; cannot getPendingIntent");
    364         }
    365 
    366         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
    367         intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    368                 IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
    369                 IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
    370         // Appropriate flags will be added by the call below.
    371         return IMPL.getPendingIntent(mSourceContext, intents, requestCode, flags, options);
    372     }
    373 
    374     /**
    375      * Return an array containing the intents added to this builder. The intent at the
    376      * root of the task stack will appear as the first item in the array and the
    377      * intent at the top of the stack will appear as the last item.
    378      *
    379      * @return An array containing the intents added to this builder.
    380      */
    381     public Intent[] getIntents() {
    382         Intent[] intents = new Intent[mIntents.size()];
    383         if (intents.length == 0) return intents;
    384 
    385         intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    386                 IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
    387                 IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
    388         for (int i = 1; i < intents.length; i++) {
    389             intents[i] = new Intent(mIntents.get(i));
    390         }
    391         return intents;
    392     }
    393 }
    394