1/*
2 * Copyright (C) 2007 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.preference;
18
19import java.util.ArrayList;
20import java.util.HashSet;
21import java.util.List;
22
23import android.app.Activity;
24import android.app.Dialog;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.SharedPreferences;
29import android.content.pm.ActivityInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ResolveInfo;
32import android.content.pm.PackageManager.NameNotFoundException;
33import android.content.res.XmlResourceParser;
34import android.os.Bundle;
35import android.util.Log;
36
37/**
38 * Used to help create {@link Preference} hierarchies
39 * from activities or XML.
40 * <p>
41 * In most cases, clients should use
42 * {@link PreferenceActivity#addPreferencesFromIntent} or
43 * {@link PreferenceActivity#addPreferencesFromResource(int)}.
44 *
45 * @see PreferenceActivity
46 */
47public class PreferenceManager {
48
49    private static final String TAG = "PreferenceManager";
50
51    /**
52     * The Activity meta-data key for its XML preference hierarchy.
53     */
54    public static final String METADATA_KEY_PREFERENCES = "android.preference";
55
56    public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
57
58    /**
59     * @see #getActivity()
60     */
61    private Activity mActivity;
62
63    /**
64     * The context to use. This should always be set.
65     *
66     * @see #mActivity
67     */
68    private Context mContext;
69
70    /**
71     * The counter for unique IDs.
72     */
73    private long mNextId = 0;
74
75    /**
76     * The counter for unique request codes.
77     */
78    private int mNextRequestCode;
79
80    /**
81     * Cached shared preferences.
82     */
83    private SharedPreferences mSharedPreferences;
84
85    /**
86     * If in no-commit mode, the shared editor to give out (which will be
87     * committed when exiting no-commit mode).
88     */
89    private SharedPreferences.Editor mEditor;
90
91    /**
92     * Blocks commits from happening on the shared editor. This is used when
93     * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
94     */
95    private boolean mNoCommit;
96
97    /**
98     * The SharedPreferences name that will be used for all {@link Preference}s
99     * managed by this instance.
100     */
101    private String mSharedPreferencesName;
102
103    /**
104     * The SharedPreferences mode that will be used for all {@link Preference}s
105     * managed by this instance.
106     */
107    private int mSharedPreferencesMode;
108
109    /**
110     * The {@link PreferenceScreen} at the root of the preference hierarchy.
111     */
112    private PreferenceScreen mPreferenceScreen;
113
114    /**
115     * List of activity result listeners.
116     */
117    private List<OnActivityResultListener> mActivityResultListeners;
118
119    /**
120     * List of activity stop listeners.
121     */
122    private List<OnActivityStopListener> mActivityStopListeners;
123
124    /**
125     * List of activity destroy listeners.
126     */
127    private List<OnActivityDestroyListener> mActivityDestroyListeners;
128
129    /**
130     * List of dialogs that should be dismissed when we receive onNewIntent in
131     * our PreferenceActivity.
132     */
133    private List<DialogInterface> mPreferencesScreens;
134
135    private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
136
137    PreferenceManager(Activity activity, int firstRequestCode) {
138        mActivity = activity;
139        mNextRequestCode = firstRequestCode;
140
141        init(activity);
142    }
143
144    /**
145     * This constructor should ONLY be used when getting default values from
146     * an XML preference hierarchy.
147     * <p>
148     * The {@link PreferenceManager#PreferenceManager(Activity)}
149     * should be used ANY time a preference will be displayed, since some preference
150     * types need an Activity for managed queries.
151     */
152    private PreferenceManager(Context context) {
153        init(context);
154    }
155
156    private void init(Context context) {
157        mContext = context;
158
159        setSharedPreferencesName(getDefaultSharedPreferencesName(context));
160    }
161
162    /**
163     * Returns a list of {@link Activity} (indirectly) that match a given
164     * {@link Intent}.
165     *
166     * @param queryIntent The Intent to match.
167     * @return The list of {@link ResolveInfo} that point to the matched
168     *         activities.
169     */
170    private List<ResolveInfo> queryIntentActivities(Intent queryIntent) {
171        return mContext.getPackageManager().queryIntentActivities(queryIntent,
172                PackageManager.GET_META_DATA);
173    }
174
175    /**
176     * Inflates a preference hierarchy from the preference hierarchies of
177     * {@link Activity Activities} that match the given {@link Intent}. An
178     * {@link Activity} defines its preference hierarchy with meta-data using
179     * the {@link #METADATA_KEY_PREFERENCES} key.
180     * <p>
181     * If a preference hierarchy is given, the new preference hierarchies will
182     * be merged in.
183     *
184     * @param queryIntent The intent to match activities.
185     * @param rootPreferences Optional existing hierarchy to merge the new
186     *            hierarchies into.
187     * @return The root hierarchy (if one was not provided, the new hierarchy's
188     *         root).
189     */
190    PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
191        final List<ResolveInfo> activities = queryIntentActivities(queryIntent);
192        final HashSet<String> inflatedRes = new HashSet<String>();
193
194        for (int i = activities.size() - 1; i >= 0; i--) {
195            final ActivityInfo activityInfo = activities.get(i).activityInfo;
196            final Bundle metaData = activityInfo.metaData;
197
198            if ((metaData == null) || !metaData.containsKey(METADATA_KEY_PREFERENCES)) {
199                continue;
200            }
201
202            // Need to concat the package with res ID since the same res ID
203            // can be re-used across contexts
204            final String uniqueResId = activityInfo.packageName + ":"
205                    + activityInfo.metaData.getInt(METADATA_KEY_PREFERENCES);
206
207            if (!inflatedRes.contains(uniqueResId)) {
208                inflatedRes.add(uniqueResId);
209
210                final Context context;
211                try {
212                    context = mContext.createPackageContext(activityInfo.packageName, 0);
213                } catch (NameNotFoundException e) {
214                    Log.w(TAG, "Could not create context for " + activityInfo.packageName + ": "
215                        + Log.getStackTraceString(e));
216                    continue;
217                }
218
219                final PreferenceInflater inflater = new PreferenceInflater(context, this);
220                final XmlResourceParser parser = activityInfo.loadXmlMetaData(context
221                        .getPackageManager(), METADATA_KEY_PREFERENCES);
222                rootPreferences = (PreferenceScreen) inflater
223                        .inflate(parser, rootPreferences, true);
224                parser.close();
225            }
226        }
227
228        rootPreferences.onAttachedToHierarchy(this);
229
230        return rootPreferences;
231    }
232
233    /**
234     * Inflates a preference hierarchy from XML. If a preference hierarchy is
235     * given, the new preference hierarchies will be merged in.
236     *
237     * @param context The context of the resource.
238     * @param resId The resource ID of the XML to inflate.
239     * @param rootPreferences Optional existing hierarchy to merge the new
240     *            hierarchies into.
241     * @return The root hierarchy (if one was not provided, the new hierarchy's
242     *         root).
243     * @hide
244     */
245    public PreferenceScreen inflateFromResource(Context context, int resId,
246            PreferenceScreen rootPreferences) {
247        // Block commits
248        setNoCommit(true);
249
250        final PreferenceInflater inflater = new PreferenceInflater(context, this);
251        rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true);
252        rootPreferences.onAttachedToHierarchy(this);
253
254        // Unblock commits
255        setNoCommit(false);
256
257        return rootPreferences;
258    }
259
260    public PreferenceScreen createPreferenceScreen(Context context) {
261        final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
262        preferenceScreen.onAttachedToHierarchy(this);
263        return preferenceScreen;
264    }
265
266    /**
267     * Called by a preference to get a unique ID in its hierarchy.
268     *
269     * @return A unique ID.
270     */
271    long getNextId() {
272        synchronized (this) {
273            return mNextId++;
274        }
275    }
276
277    /**
278     * Returns the current name of the SharedPreferences file that preferences managed by
279     * this will use.
280     *
281     * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
282     * @see Context#getSharedPreferences(String, int)
283     */
284    public String getSharedPreferencesName() {
285        return mSharedPreferencesName;
286    }
287
288    /**
289     * Sets the name of the SharedPreferences file that preferences managed by this
290     * will use.
291     *
292     * @param sharedPreferencesName The name of the SharedPreferences file.
293     * @see Context#getSharedPreferences(String, int)
294     */
295    public void setSharedPreferencesName(String sharedPreferencesName) {
296        mSharedPreferencesName = sharedPreferencesName;
297        mSharedPreferences = null;
298    }
299
300    /**
301     * Returns the current mode of the SharedPreferences file that preferences managed by
302     * this will use.
303     *
304     * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
305     * @see Context#getSharedPreferences(String, int)
306     */
307    public int getSharedPreferencesMode() {
308        return mSharedPreferencesMode;
309    }
310
311    /**
312     * Sets the mode of the SharedPreferences file that preferences managed by this
313     * will use.
314     *
315     * @param sharedPreferencesMode The mode of the SharedPreferences file.
316     * @see Context#getSharedPreferences(String, int)
317     */
318    public void setSharedPreferencesMode(int sharedPreferencesMode) {
319        mSharedPreferencesMode = sharedPreferencesMode;
320        mSharedPreferences = null;
321    }
322
323    /**
324     * Gets a SharedPreferences instance that preferences managed by this will
325     * use.
326     *
327     * @return A SharedPreferences instance pointing to the file that contains
328     *         the values of preferences that are managed by this.
329     */
330    public SharedPreferences getSharedPreferences() {
331        if (mSharedPreferences == null) {
332            mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName,
333                    mSharedPreferencesMode);
334        }
335
336        return mSharedPreferences;
337    }
338
339    /**
340     * Gets a SharedPreferences instance that points to the default file that is
341     * used by the preference framework in the given context.
342     *
343     * @param context The context of the preferences whose values are wanted.
344     * @return A SharedPreferences instance that can be used to retrieve and
345     *         listen to values of the preferences.
346     */
347    public static SharedPreferences getDefaultSharedPreferences(Context context) {
348        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
349                getDefaultSharedPreferencesMode());
350    }
351
352    private static String getDefaultSharedPreferencesName(Context context) {
353        return context.getPackageName() + "_preferences";
354    }
355
356    private static int getDefaultSharedPreferencesMode() {
357        return Context.MODE_PRIVATE;
358    }
359
360    /**
361     * Returns the root of the preference hierarchy managed by this class.
362     *
363     * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
364     */
365    PreferenceScreen getPreferenceScreen() {
366        return mPreferenceScreen;
367    }
368
369    /**
370     * Sets the root of the preference hierarchy.
371     *
372     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
373     * @return Whether the {@link PreferenceScreen} given is different than the previous.
374     */
375    boolean setPreferences(PreferenceScreen preferenceScreen) {
376        if (preferenceScreen != mPreferenceScreen) {
377            mPreferenceScreen = preferenceScreen;
378            return true;
379        }
380
381        return false;
382    }
383
384    /**
385     * Finds a {@link Preference} based on its key.
386     *
387     * @param key The key of the preference to retrieve.
388     * @return The {@link Preference} with the key, or null.
389     * @see PreferenceGroup#findPreference(CharSequence)
390     */
391    public Preference findPreference(CharSequence key) {
392        if (mPreferenceScreen == null) {
393            return null;
394        }
395
396        return mPreferenceScreen.findPreference(key);
397    }
398
399    /**
400     * Sets the default values from a preference hierarchy in XML. This should
401     * be called by the application's main activity.
402     * <p>
403     * If {@code readAgain} is false, this will only set the default values if this
404     * method has never been called in the past (or the
405     * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
406     * preferences file is false). To attempt to set the default values again
407     * bypassing this check, set {@code readAgain} to true.
408     *
409     * @param context The context of the shared preferences.
410     * @param resId The resource ID of the preference hierarchy XML file.
411     * @param readAgain Whether to re-read the default values.
412     *            <p>
413     *            Note: this will NOT reset preferences back to their default
414     *            values. For that functionality, use
415     *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
416     *            and clear it followed by a call to this method with this
417     *            parameter set to true.
418     */
419    public static void setDefaultValues(Context context, int resId, boolean readAgain) {
420
421        // Use the default shared preferences name and mode
422        setDefaultValues(context, getDefaultSharedPreferencesName(context),
423                getDefaultSharedPreferencesMode(), resId, readAgain);
424    }
425
426    /**
427     * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
428     * the client to provide the filename and mode of the shared preferences
429     * file.
430     *
431     * @see #setDefaultValues(Context, int, boolean)
432     * @see #setSharedPreferencesName(String)
433     * @see #setSharedPreferencesMode(int)
434     */
435    public static void setDefaultValues(Context context, String sharedPreferencesName,
436            int sharedPreferencesMode, int resId, boolean readAgain) {
437        final SharedPreferences defaultValueSp = context.getSharedPreferences(
438                KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
439
440        if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
441            final PreferenceManager pm = new PreferenceManager(context);
442            pm.setSharedPreferencesName(sharedPreferencesName);
443            pm.setSharedPreferencesMode(sharedPreferencesMode);
444            pm.inflateFromResource(context, resId, null);
445
446            SharedPreferences.Editor editor =
447                    defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
448            try {
449                editor.apply();
450            } catch (AbstractMethodError unused) {
451                // The app injected its own pre-Gingerbread
452                // SharedPreferences.Editor implementation without
453                // an apply method.
454                editor.commit();
455            }
456        }
457    }
458
459    /**
460     * Returns an editor to use when modifying the shared preferences.
461     * <p>
462     * Do NOT commit unless {@link #shouldCommit()} returns true.
463     *
464     * @return An editor to use to write to shared preferences.
465     * @see #shouldCommit()
466     */
467    SharedPreferences.Editor getEditor() {
468
469        if (mNoCommit) {
470            if (mEditor == null) {
471                mEditor = getSharedPreferences().edit();
472            }
473
474            return mEditor;
475        } else {
476            return getSharedPreferences().edit();
477        }
478    }
479
480    /**
481     * Whether it is the client's responsibility to commit on the
482     * {@link #getEditor()}. This will return false in cases where the writes
483     * should be batched, for example when inflating preferences from XML.
484     *
485     * @return Whether the client should commit.
486     */
487    boolean shouldCommit() {
488        return !mNoCommit;
489    }
490
491    private void setNoCommit(boolean noCommit) {
492        if (!noCommit && mEditor != null) {
493            try {
494                mEditor.apply();
495            } catch (AbstractMethodError unused) {
496                // The app injected its own pre-Gingerbread
497                // SharedPreferences.Editor implementation without
498                // an apply method.
499                mEditor.commit();
500            }
501        }
502        mNoCommit = noCommit;
503    }
504
505    /**
506     * Returns the activity that shows the preferences. This is useful for doing
507     * managed queries, but in most cases the use of {@link #getContext()} is
508     * preferred.
509     * <p>
510     * This will return null if this class was instantiated with a Context
511     * instead of Activity. For example, when setting the default values.
512     *
513     * @return The activity that shows the preferences.
514     * @see #mContext
515     */
516    Activity getActivity() {
517        return mActivity;
518    }
519
520    /**
521     * Returns the context. This is preferred over {@link #getActivity()} when
522     * possible.
523     *
524     * @return The context.
525     */
526    Context getContext() {
527        return mContext;
528    }
529
530    /**
531     * Registers a listener.
532     *
533     * @see OnActivityResultListener
534     */
535    void registerOnActivityResultListener(OnActivityResultListener listener) {
536        synchronized (this) {
537            if (mActivityResultListeners == null) {
538                mActivityResultListeners = new ArrayList<OnActivityResultListener>();
539            }
540
541            if (!mActivityResultListeners.contains(listener)) {
542                mActivityResultListeners.add(listener);
543            }
544        }
545    }
546
547    /**
548     * Unregisters a listener.
549     *
550     * @see OnActivityResultListener
551     */
552    void unregisterOnActivityResultListener(OnActivityResultListener listener) {
553        synchronized (this) {
554            if (mActivityResultListeners != null) {
555                mActivityResultListeners.remove(listener);
556            }
557        }
558    }
559
560    /**
561     * Called by the {@link PreferenceManager} to dispatch a subactivity result.
562     */
563    void dispatchActivityResult(int requestCode, int resultCode, Intent data) {
564        List<OnActivityResultListener> list;
565
566        synchronized (this) {
567            if (mActivityResultListeners == null) return;
568            list = new ArrayList<OnActivityResultListener>(mActivityResultListeners);
569        }
570
571        final int N = list.size();
572        for (int i = 0; i < N; i++) {
573            if (list.get(i).onActivityResult(requestCode, resultCode, data)) {
574                break;
575            }
576        }
577    }
578
579    /**
580     * Registers a listener.
581     *
582     * @see OnActivityStopListener
583     */
584    void registerOnActivityStopListener(OnActivityStopListener listener) {
585        synchronized (this) {
586            if (mActivityStopListeners == null) {
587                mActivityStopListeners = new ArrayList<OnActivityStopListener>();
588            }
589
590            if (!mActivityStopListeners.contains(listener)) {
591                mActivityStopListeners.add(listener);
592            }
593        }
594    }
595
596    /**
597     * Unregisters a listener.
598     *
599     * @see OnActivityStopListener
600     */
601    void unregisterOnActivityStopListener(OnActivityStopListener listener) {
602        synchronized (this) {
603            if (mActivityStopListeners != null) {
604                mActivityStopListeners.remove(listener);
605            }
606        }
607    }
608
609    /**
610     * Called by the {@link PreferenceManager} to dispatch the activity stop
611     * event.
612     */
613    void dispatchActivityStop() {
614        List<OnActivityStopListener> list;
615
616        synchronized (this) {
617            if (mActivityStopListeners == null) return;
618            list = new ArrayList<OnActivityStopListener>(mActivityStopListeners);
619        }
620
621        final int N = list.size();
622        for (int i = 0; i < N; i++) {
623            list.get(i).onActivityStop();
624        }
625    }
626
627    /**
628     * Registers a listener.
629     *
630     * @see OnActivityDestroyListener
631     */
632    void registerOnActivityDestroyListener(OnActivityDestroyListener listener) {
633        synchronized (this) {
634            if (mActivityDestroyListeners == null) {
635                mActivityDestroyListeners = new ArrayList<OnActivityDestroyListener>();
636            }
637
638            if (!mActivityDestroyListeners.contains(listener)) {
639                mActivityDestroyListeners.add(listener);
640            }
641        }
642    }
643
644    /**
645     * Unregisters a listener.
646     *
647     * @see OnActivityDestroyListener
648     */
649    void unregisterOnActivityDestroyListener(OnActivityDestroyListener listener) {
650        synchronized (this) {
651            if (mActivityDestroyListeners != null) {
652                mActivityDestroyListeners.remove(listener);
653            }
654        }
655    }
656
657    /**
658     * Called by the {@link PreferenceManager} to dispatch the activity destroy
659     * event.
660     */
661    void dispatchActivityDestroy() {
662        List<OnActivityDestroyListener> list = null;
663
664        synchronized (this) {
665            if (mActivityDestroyListeners != null) {
666                list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
667            }
668        }
669
670        if (list != null) {
671            final int N = list.size();
672            for (int i = 0; i < N; i++) {
673                list.get(i).onActivityDestroy();
674            }
675        }
676
677        // Dismiss any PreferenceScreens still showing
678        dismissAllScreens();
679    }
680
681    /**
682     * Returns a request code that is unique for the activity. Each subsequent
683     * call to this method should return another unique request code.
684     *
685     * @return A unique request code that will never be used by anyone other
686     *         than the caller of this method.
687     */
688    int getNextRequestCode() {
689        synchronized (this) {
690            return mNextRequestCode++;
691        }
692    }
693
694    void addPreferencesScreen(DialogInterface screen) {
695        synchronized (this) {
696
697            if (mPreferencesScreens == null) {
698                mPreferencesScreens = new ArrayList<DialogInterface>();
699            }
700
701            mPreferencesScreens.add(screen);
702        }
703    }
704
705    void removePreferencesScreen(DialogInterface screen) {
706        synchronized (this) {
707
708            if (mPreferencesScreens == null) {
709                return;
710            }
711
712            mPreferencesScreens.remove(screen);
713        }
714    }
715
716    /**
717     * Called by {@link PreferenceActivity} to dispatch the new Intent event.
718     *
719     * @param intent The new Intent.
720     */
721    void dispatchNewIntent(Intent intent) {
722        dismissAllScreens();
723    }
724
725    private void dismissAllScreens() {
726        // Remove any of the previously shown preferences screens
727        ArrayList<DialogInterface> screensToDismiss;
728
729        synchronized (this) {
730
731            if (mPreferencesScreens == null) {
732                return;
733            }
734
735            screensToDismiss = new ArrayList<DialogInterface>(mPreferencesScreens);
736            mPreferencesScreens.clear();
737        }
738
739        for (int i = screensToDismiss.size() - 1; i >= 0; i--) {
740            screensToDismiss.get(i).dismiss();
741        }
742    }
743
744    /**
745     * Sets the callback to be invoked when a {@link Preference} in the
746     * hierarchy rooted at this {@link PreferenceManager} is clicked.
747     *
748     * @param listener The callback to be invoked.
749     */
750    void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
751        mOnPreferenceTreeClickListener = listener;
752    }
753
754    OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
755        return mOnPreferenceTreeClickListener;
756    }
757
758    /**
759     * Interface definition for a callback to be invoked when a
760     * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
761     * clicked.
762     */
763    interface OnPreferenceTreeClickListener {
764        /**
765         * Called when a preference in the tree rooted at this
766         * {@link PreferenceScreen} has been clicked.
767         *
768         * @param preferenceScreen The {@link PreferenceScreen} that the
769         *        preference is located in.
770         * @param preference The preference that was clicked.
771         * @return Whether the click was handled.
772         */
773        boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference);
774    }
775
776    /**
777     * Interface definition for a class that will be called when the container's activity
778     * receives an activity result.
779     */
780    public interface OnActivityResultListener {
781
782        /**
783         * See Activity's onActivityResult.
784         *
785         * @return Whether the request code was handled (in which case
786         *         subsequent listeners will not be called.
787         */
788        boolean onActivityResult(int requestCode, int resultCode, Intent data);
789    }
790
791    /**
792     * Interface definition for a class that will be called when the container's activity
793     * is stopped.
794     */
795    public interface OnActivityStopListener {
796
797        /**
798         * See Activity's onStop.
799         */
800        void onActivityStop();
801    }
802
803    /**
804     * Interface definition for a class that will be called when the container's activity
805     * is destroyed.
806     */
807    public interface OnActivityDestroyListener {
808
809        /**
810         * See Activity's onDestroy.
811         */
812        void onActivityDestroy();
813    }
814
815}
816