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