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