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