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 androidx.core.app; 18 19 import android.app.Activity; 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.Build; 27 import android.util.Log; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 /** 33 * NavUtils provides helper functionality for applications implementing 34 * recommended Android UI navigation patterns. For information about recommended 35 * navigation patterns see 36 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> 37 * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> 38 * from the design guide. 39 */ 40 public final class NavUtils { 41 private static final String TAG = "NavUtils"; 42 public static final String PARENT_ACTIVITY = "android.support.PARENT_ACTIVITY"; 43 44 /** 45 * Returns true if sourceActivity should recreate the task when navigating 'up' 46 * by using targetIntent. 47 * 48 * <p>If this method returns false the app can trivially call 49 * {@link #navigateUpTo(Activity, Intent)} using the same parameters to correctly perform 50 * up navigation. If this method returns true, the app should synthesize a new task stack 51 * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.</p> 52 * 53 * @param sourceActivity The current activity from which the user is attempting to navigate up 54 * @param targetIntent An intent representing the target destination for up navigation 55 * @return true if navigating up should recreate a new task stack, false if the same task 56 * should be used for the destination 57 */ shouldUpRecreateTask(@onNull Activity sourceActivity, @NonNull Intent targetIntent)58 public static boolean shouldUpRecreateTask(@NonNull Activity sourceActivity, 59 @NonNull Intent targetIntent) { 60 if (Build.VERSION.SDK_INT >= 16) { 61 return sourceActivity.shouldUpRecreateTask(targetIntent); 62 } else { 63 String action = sourceActivity.getIntent().getAction(); 64 return action != null && !action.equals(Intent.ACTION_MAIN); 65 } 66 } 67 68 /** 69 * Convenience method that is equivalent to calling 70 * <code>{@link #navigateUpTo(Activity, Intent) navigateUpTo}(sourceActivity, 71 * {@link #getParentActivityIntent(Activity) getParentActivityIntent} (sourceActivity))</code>. 72 * sourceActivity will be finished by this call. 73 * 74 * <p><em>Note:</em> This method should only be used when sourceActivity and the corresponding 75 * parent are within the same task. If up navigation should cross tasks in some cases, see 76 * {@link #shouldUpRecreateTask(Activity, Intent)}.</p> 77 * 78 * @param sourceActivity The current activity from which the user is attempting to navigate up 79 */ navigateUpFromSameTask(@onNull Activity sourceActivity)80 public static void navigateUpFromSameTask(@NonNull Activity sourceActivity) { 81 Intent upIntent = getParentActivityIntent(sourceActivity); 82 83 if (upIntent == null) { 84 throw new IllegalArgumentException("Activity " + 85 sourceActivity.getClass().getSimpleName() + 86 " does not have a parent activity name specified." + 87 " (Did you forget to add the android.support.PARENT_ACTIVITY <meta-data> " + 88 " element in your manifest?)"); 89 } 90 91 navigateUpTo(sourceActivity, upIntent); 92 } 93 94 /** 95 * Navigate from sourceActivity to the activity specified by upIntent, finishing sourceActivity 96 * in the process. upIntent will have the flag {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} set 97 * by this method, along with any others required for proper up navigation as outlined 98 * in the Android Design Guide. 99 * 100 * <p>This method should be used when performing up navigation from within the same task 101 * as the destination. If up navigation should cross tasks in some cases, see 102 * {@link #shouldUpRecreateTask(Activity, Intent)}.</p> 103 * 104 * @param sourceActivity The current activity from which the user is attempting to navigate up 105 * @param upIntent An intent representing the target destination for up navigation 106 */ navigateUpTo(@onNull Activity sourceActivity, @NonNull Intent upIntent)107 public static void navigateUpTo(@NonNull Activity sourceActivity, @NonNull Intent upIntent) { 108 if (Build.VERSION.SDK_INT >= 16) { 109 sourceActivity.navigateUpTo(upIntent); 110 } else { 111 upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 112 sourceActivity.startActivity(upIntent); 113 sourceActivity.finish(); 114 } 115 } 116 117 /** 118 * Obtain an {@link Intent} that will launch an explicit target activity 119 * specified by sourceActivity's {@link #PARENT_ACTIVITY} <meta-data> 120 * element in the application's manifest. If the device is running 121 * Jellybean or newer, the android:parentActivityName attribute will be preferred 122 * if it is present. 123 * 124 * @param sourceActivity Activity to fetch a parent intent for 125 * @return a new Intent targeting the defined parent activity of sourceActivity 126 */ 127 @Nullable getParentActivityIntent(@onNull Activity sourceActivity)128 public static Intent getParentActivityIntent(@NonNull Activity sourceActivity) { 129 if (Build.VERSION.SDK_INT >= 16) { 130 // Prefer the "real" JB definition if available, 131 // else fall back to the meta-data element. 132 Intent result = sourceActivity.getParentActivityIntent(); 133 if (result != null) { 134 return result; 135 } 136 } 137 String parentName = NavUtils.getParentActivityName(sourceActivity); 138 if (parentName == null) return null; 139 140 // If the parent itself has no parent, generate a main activity intent. 141 final ComponentName target = new ComponentName(sourceActivity, parentName); 142 try { 143 final String grandparent = NavUtils.getParentActivityName(sourceActivity, target); 144 return grandparent == null 145 ? Intent.makeMainActivity(target) 146 : new Intent().setComponent(target); 147 } catch (NameNotFoundException e) { 148 Log.e(TAG, "getParentActivityIntent: bad parentActivityName '" + parentName 149 + "' in manifest"); 150 return null; 151 } 152 } 153 154 /** 155 * Obtain an {@link Intent} that will launch an explicit target activity 156 * specified by sourceActivityClass's {@link #PARENT_ACTIVITY} <meta-data> 157 * element in the application's manifest. 158 * 159 * @param context Context for looking up the activity component for sourceActivityClass 160 * @param sourceActivityClass {@link java.lang.Class} object for an Activity class 161 * @return a new Intent targeting the defined parent activity of sourceActivity 162 * @throws NameNotFoundException if the ComponentName for sourceActivityClass is invalid 163 */ 164 @Nullable getParentActivityIntent(@onNull Context context, @NonNull Class<?> sourceActivityClass)165 public static Intent getParentActivityIntent(@NonNull Context context, 166 @NonNull Class<?> sourceActivityClass) 167 throws NameNotFoundException { 168 String parentActivity = getParentActivityName(context, 169 new ComponentName(context, sourceActivityClass)); 170 if (parentActivity == null) return null; 171 172 // If the parent itself has no parent, generate a main activity intent. 173 final ComponentName target = new ComponentName(context, parentActivity); 174 final String grandparent = getParentActivityName(context, target); 175 final Intent parentIntent = grandparent == null 176 ? Intent.makeMainActivity(target) 177 : new Intent().setComponent(target); 178 return parentIntent; 179 } 180 181 /** 182 * Obtain an {@link Intent} that will launch an explicit target activity 183 * specified by sourceActivityClass's {@link #PARENT_ACTIVITY} <meta-data> 184 * element in the application's manifest. 185 * 186 * @param context Context for looking up the activity component for the source activity 187 * @param componentName ComponentName for the source Activity 188 * @return a new Intent targeting the defined parent activity of sourceActivity 189 * @throws NameNotFoundException if the ComponentName for sourceActivityClass is invalid 190 */ 191 @Nullable getParentActivityIntent(@onNull Context context, @NonNull ComponentName componentName)192 public static Intent getParentActivityIntent(@NonNull Context context, 193 @NonNull ComponentName componentName) 194 throws NameNotFoundException { 195 String parentActivity = getParentActivityName(context, componentName); 196 if (parentActivity == null) return null; 197 198 // If the parent itself has no parent, generate a main activity intent. 199 final ComponentName target = new ComponentName( 200 componentName.getPackageName(), parentActivity); 201 final String grandparent = getParentActivityName(context, target); 202 final Intent parentIntent = grandparent == null 203 ? Intent.makeMainActivity(target) 204 : new Intent().setComponent(target); 205 return parentIntent; 206 } 207 208 /** 209 * Return the fully qualified class name of sourceActivity's parent activity as specified by 210 * a {@link #PARENT_ACTIVITY} <meta-data> element within the activity element in 211 * the application's manifest. 212 * 213 * @param sourceActivity Activity to fetch a parent class name for 214 * @return The fully qualified class name of sourceActivity's parent activity or null if 215 * it was not specified 216 */ 217 @Nullable getParentActivityName(@onNull Activity sourceActivity)218 public static String getParentActivityName(@NonNull Activity sourceActivity) { 219 try { 220 return getParentActivityName(sourceActivity, sourceActivity.getComponentName()); 221 } catch (NameNotFoundException e) { 222 // Component name of supplied activity does not exist...? 223 throw new IllegalArgumentException(e); 224 } 225 } 226 /** 227 * Return the fully qualified class name of a source activity's parent activity as specified by 228 * a {@link #PARENT_ACTIVITY} <meta-data> element within the activity element in 229 * the application's manifest. The source activity is provided by componentName. 230 * 231 * @param context Context for looking up the activity component for the source activity 232 * @param componentName ComponentName for the source Activity 233 * @return The fully qualified class name of sourceActivity's parent activity or null if 234 * it was not specified 235 */ 236 @Nullable getParentActivityName(@onNull Context context, @NonNull ComponentName componentName)237 public static String getParentActivityName(@NonNull Context context, 238 @NonNull ComponentName componentName) 239 throws NameNotFoundException { 240 PackageManager pm = context.getPackageManager(); 241 ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA); 242 if (Build.VERSION.SDK_INT >= 16) { 243 String result = info.parentActivityName; 244 if (result != null) { 245 return result; 246 } 247 } 248 if (info.metaData == null) { 249 return null; 250 } 251 String parentActivity = info.metaData.getString(PARENT_ACTIVITY); 252 if (parentActivity == null) { 253 return null; 254 } 255 if (parentActivity.charAt(0) == '.') { 256 parentActivity = context.getPackageName() + parentActivity; 257 } 258 return parentActivity; 259 } 260 261 /** No instances! */ NavUtils()262 private NavUtils() { 263 } 264 } 265