Home | History | Annotate | Download | only in navigation
      1 /*
      2  * Copyright (C) 2017 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 androidx.navigation;
     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.os.Bundle;
     25 import android.support.annotation.IdRes;
     26 import android.support.annotation.NavigationRes;
     27 import android.support.annotation.NonNull;
     28 import android.support.annotation.Nullable;
     29 import android.support.v4.app.TaskStackBuilder;
     30 
     31 import java.util.ArrayDeque;
     32 
     33 /**
     34  * Class used to construct deep links to a particular destination in a {@link NavGraph}.
     35  *
     36  * <p>When this deep link is triggered:
     37  * <ol>
     38  *     <li>The task is cleared.</li>
     39  *     <li>The destination and all of its parents will be on the back stack.</li>
     40  *     <li>Calling {@link NavController#navigateUp()} will navigate to the parent of the
     41  *     destination.</li>
     42  * </ol></p>
     43  *
     44  * The parent of the destination is the {@link NavGraph#getStartDestination() start destination}
     45  * of the containing {@link NavGraph navigation graph}. In the cases where the destination is
     46  * the start destination of its containing navigation graph, the start destination of its
     47  * grandparent is used.
     48  * <p>
     49  * You can construct an instance directly with {@link #NavDeepLinkBuilder(Context)} or build one
     50  * using an existing {@link NavController} via {@link NavController#createDeepLink()}.
     51  */
     52 public class NavDeepLinkBuilder {
     53     private final Context mContext;
     54     private final Intent mIntent;
     55 
     56     private NavGraph mGraph;
     57     private int mDestId;
     58 
     59     /**
     60      * Construct a new NavDeepLinkBuilder.
     61      *
     62      * If the context passed in here is not an {@link Activity}, this method will use
     63      * {@link android.content.pm.PackageManager#getLaunchIntentForPackage(String)} as the
     64      * default activity to launch, if available.
     65      *
     66      * @param context Context used to create deep links
     67      * @see #setComponentName
     68      */
     69     public NavDeepLinkBuilder(@NonNull Context context) {
     70         mContext = context;
     71         if (mContext instanceof Activity) {
     72             mIntent = new Intent(mContext, mContext.getClass());
     73         } else {
     74             Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(
     75                     mContext.getPackageName());
     76             mIntent = launchIntent != null ? launchIntent : new Intent();
     77         }
     78         mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
     79     }
     80 
     81     /**
     82      * @see NavController#createDeepLink()
     83      */
     84     NavDeepLinkBuilder(@NonNull NavController navController) {
     85         this(navController.getContext());
     86         mGraph = navController.getGraph();
     87     }
     88 
     89     /**
     90      * Sets an explicit Activity to be started by the deep link created by this class.
     91      *
     92      * @param activityClass The Activity to start. This Activity should have a {@link NavController}
     93      *                      which uses the same {@link NavGraph} used to construct this
     94      *                      deep link.
     95      * @return this object for chaining
     96      */
     97     @NonNull
     98     public NavDeepLinkBuilder setComponentName(@NonNull Class<? extends Activity> activityClass) {
     99         return setComponentName(new ComponentName(mContext, activityClass));
    100     }
    101 
    102     /**
    103      * Sets an explicit Activity to be started by the deep link created by this class.
    104      *
    105      * @param componentName The Activity to start. This Activity should have a {@link NavController}
    106      *                      which uses the same {@link NavGraph} used to construct this
    107      *                      deep link.
    108      * @return this object for chaining
    109      */
    110     @NonNull
    111     public NavDeepLinkBuilder setComponentName(@NonNull ComponentName componentName) {
    112         mIntent.setComponent(componentName);
    113         return this;
    114     }
    115 
    116     /**
    117      * Sets the graph that contains the {@link #setDestination(int) deep link destination}.
    118      *
    119      * @param navGraphId ID of the {@link NavGraph} containing the deep link destination
    120      * @return this object for chaining
    121      */
    122     @NonNull
    123     public NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId) {
    124         return setGraph(new NavInflater(mContext, new PermissiveNavigatorProvider(mContext))
    125                 .inflate(navGraphId));
    126     }
    127 
    128     /**
    129      * Sets the graph that contains the {@link #setDestination(int) deep link destination}.
    130      *
    131      * @param navGraph The {@link NavGraph} containing the deep link destination
    132      * @return this object for chaining
    133      */
    134     @NonNull
    135     public NavDeepLinkBuilder setGraph(@NonNull NavGraph navGraph) {
    136         mGraph = navGraph;
    137         if (mDestId != 0) {
    138             fillInIntent();
    139         }
    140         return this;
    141     }
    142 
    143     /**
    144      * Sets the destination id to deep link to.
    145      *
    146      * @param destId destination ID to deep link to.
    147      * @return this object for chaining
    148      */
    149     @NonNull
    150     public NavDeepLinkBuilder setDestination(@IdRes int destId) {
    151         mDestId = destId;
    152         if (mGraph != null) {
    153             fillInIntent();
    154         }
    155         return this;
    156     }
    157 
    158     private void fillInIntent() {
    159         NavDestination node = null;
    160         ArrayDeque<NavDestination> possibleDestinations = new ArrayDeque<>();
    161         possibleDestinations.add(mGraph);
    162         while (!possibleDestinations.isEmpty() && node == null) {
    163             NavDestination destination = possibleDestinations.poll();
    164             if (destination.getId() == mDestId) {
    165                 node = destination;
    166             } else if (destination instanceof NavGraph) {
    167                 for (NavDestination child : (NavGraph) destination) {
    168                     possibleDestinations.add(child);
    169                 }
    170             }
    171         }
    172         if (node == null) {
    173             final String dest = NavDestination.getDisplayName(mContext, mDestId);
    174             throw new IllegalArgumentException("navigation destination " + dest
    175                     + " is unknown to this NavController");
    176         }
    177         mIntent.putExtra(NavController.KEY_DEEP_LINK_IDS, node.buildDeepLinkIds());
    178     }
    179 
    180     /**
    181      * Set optional arguments to send onto the destination
    182      * @param args arguments to pass to the destination
    183      * @return this object for chaining
    184      */
    185     @NonNull
    186     public NavDeepLinkBuilder setArguments(@Nullable Bundle args) {
    187         mIntent.putExtra(NavController.KEY_DEEP_LINK_EXTRAS, args);
    188         return this;
    189     }
    190 
    191     /**
    192      * Construct the full {@link TaskStackBuilder task stack} needed to deep link to the given
    193      * destination.
    194      * <p>
    195      * You must have {@link #setGraph set a NavGraph} and {@link #setDestination set a destination}
    196      * before calling this method.
    197      * </p>
    198      *
    199      * @return a {@link TaskStackBuilder} which can be used to
    200      * {@link TaskStackBuilder#startActivities() send the deep link} or
    201      * {@link TaskStackBuilder#getPendingIntent(int, int) create a PendingIntent} to deep link to
    202      * the given destination.
    203      */
    204     @NonNull
    205     public TaskStackBuilder createTaskStackBuilder() {
    206         if (mIntent.getIntArrayExtra(NavController.KEY_DEEP_LINK_IDS) == null) {
    207             if (mGraph == null) {
    208                 throw new IllegalStateException("You must call setGraph() "
    209                         + "before constructing the deep link");
    210             } else {
    211                 throw new IllegalStateException("You must call setDestination() "
    212                         + "before constructing the deep link");
    213             }
    214         }
    215         // We create a copy of the Intent to ensure the Intent does not have itself
    216         // as an extra. This also prevents developers from modifying the internal Intent
    217         // via taskStackBuilder.editIntentAt()
    218         TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext)
    219                 .addNextIntentWithParentStack(new Intent(mIntent));
    220         for (int index = 0; index < taskStackBuilder.getIntentCount(); index++) {
    221             // Attach the original Intent to each Activity so that they can know
    222             // they were constructed in response to a deep link
    223             taskStackBuilder.editIntentAt(index)
    224                     .putExtra(NavController.KEY_DEEP_LINK_INTENT, mIntent);
    225         }
    226         return taskStackBuilder;
    227     }
    228 
    229     /**
    230      * Construct a {@link PendingIntent} to the {@link #setDestination(int) deep link destination}.
    231      * <p>
    232      * This constructs the entire {@link #createTaskStackBuilder() task stack} needed.
    233      * <p>
    234      * You must have {@link #setGraph set a NavGraph} and {@link #setDestination set a destination}
    235      * before calling this method.
    236      * </p>
    237      *
    238      * @return a PendingIntent constructed with
    239      * {@link TaskStackBuilder#getPendingIntent(int, int)} to deep link to the
    240      * given destination
    241      */
    242     @NonNull
    243     public PendingIntent createPendingIntent() {
    244         return createTaskStackBuilder()
    245                 .getPendingIntent(mDestId, PendingIntent.FLAG_UPDATE_CURRENT);
    246     }
    247 
    248     /**
    249      * A {@link NavigatorProvider} that only parses the basics: {@link NavGraph navigation graphs}
    250      * and {@link NavDestination destinations}, effectively only getting the base destination
    251      * information.
    252      */
    253     @SuppressWarnings("unchecked")
    254     private static class PermissiveNavigatorProvider extends SimpleNavigatorProvider {
    255         /**
    256          * A Navigator that only parses the {@link NavDestination} attributes.
    257          */
    258         private final Navigator<NavDestination> mDestNavigator = new Navigator<NavDestination>() {
    259             @NonNull
    260             @Override
    261             public NavDestination createDestination() {
    262                 return new NavDestination(this);
    263             }
    264 
    265             @Override
    266             public void navigate(@NonNull NavDestination destination, @Nullable Bundle args,
    267                     @Nullable NavOptions navOptions) {
    268                 throw new IllegalStateException("navigate is not supported");
    269             }
    270 
    271             @Override
    272             public boolean popBackStack() {
    273                 throw new IllegalStateException("popBackStack is not supported");
    274             }
    275         };
    276 
    277         PermissiveNavigatorProvider(Context context) {
    278             addNavigator(new NavGraphNavigator(context));
    279         }
    280 
    281         @NonNull
    282         @Override
    283         public Navigator<? extends NavDestination> getNavigator(@NonNull String name) {
    284             try {
    285                 return super.getNavigator(name);
    286             } catch (IllegalStateException e) {
    287                 return mDestNavigator;
    288             }
    289         }
    290     }
    291 }
    292