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