• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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} &lt;meta-data&gt;
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} &lt;meta-data&gt;
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} &lt;meta-data&gt;
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} &lt;meta-data&gt; 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} &lt;meta-data&gt; 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