Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / base / android / java / src / org / chromium / base / ApplicationStatus.java
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.base;
6
7 import android.app.Activity;
8 import android.app.Application;
9 import android.app.Application.ActivityLifecycleCallbacks;
10 import android.content.Context;
11 import android.os.Bundle;
12
13 import java.lang.ref.WeakReference;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18
19 /**
20  * Provides information about the current activity's status, and a way
21  * to register / unregister listeners for state changes.
22  */
23 @JNINamespace("base::android")
24 public class ApplicationStatus {
25     private static class ActivityInfo {
26         private int mStatus = ActivityState.DESTROYED;
27         private ObserverList<ActivityStateListener> mListeners =
28                 new ObserverList<ActivityStateListener>();
29
30         /**
31          * @return The current {@link ActivityState} of the activity.
32          */
33         public int getStatus() {
34             return mStatus;
35         }
36
37         /**
38          * @param status The new {@link ActivityState} of the activity.
39          */
40         public void setStatus(int status) {
41             mStatus = status;
42         }
43
44         /**
45          * @return A list of {@link ActivityStateListener}s listening to this activity.
46          */
47         public ObserverList<ActivityStateListener> getListeners() {
48             return mListeners;
49         }
50     }
51
52     private static Application sApplication;
53
54     private static Integer sCachedApplicationState;
55
56     /** Last activity that was shown (or null if none or it was destroyed). */
57     private static Activity sActivity;
58
59     /** A lazily initialized listener that forwards application state changes to native. */
60     private static ApplicationStateListener sNativeApplicationStateListener;
61
62     /**
63      * A map of which observers listen to state changes from which {@link Activity}.
64      */
65     private static final Map<Activity, ActivityInfo> sActivityInfo =
66             new HashMap<Activity, ActivityInfo>();
67
68     /**
69      * A list of observers to be notified when any {@link Activity} has a state change.
70      */
71     private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners =
72             new ObserverList<ActivityStateListener>();
73
74     /**
75      * A list of observers to be notified when the visibility state of this {@link Application}
76      * changes.  See {@link #getStateForApplication()}.
77      */
78     private static final ObserverList<ApplicationStateListener> sApplicationStateListeners =
79             new ObserverList<ApplicationStateListener>();
80
81     /**
82      * Interface to be implemented by listeners.
83      */
84     public interface ApplicationStateListener {
85         /**
86          * Called when the application's state changes.
87          * @param newState The application state.
88          */
89         public void onApplicationStateChange(int newState);
90     }
91
92     /**
93      * Interface to be implemented by listeners.
94      */
95     public interface ActivityStateListener {
96         /**
97          * Called when the activity's state changes.
98          * @param activity The activity that had a state change.
99          * @param newState New activity state.
100          */
101         public void onActivityStateChange(Activity activity, int newState);
102     }
103
104     private ApplicationStatus() {}
105
106     /**
107      * Initializes the activity status for a specified application.
108      *
109      * @param application The application whose status you wish to monitor.
110      */
111     public static void initialize(Application app) {
112         sApplication = app;
113
114         ApplicationStatusManager.registerWindowFocusChangedListener(
115                 new ApplicationStatusManager.WindowFocusChangedListener() {
116             @Override
117             public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
118                 if (!hasFocus || activity == sActivity) return;
119
120                 int state = getStateForActivity(activity);
121
122                 if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) {
123                     sActivity = activity;
124                 }
125
126                 // TODO(dtrainor): Notify of active activity change?
127             }
128         });
129
130         sApplication.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
131             @Override
132             public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
133                 onStateChange(activity, ActivityState.CREATED);
134             }
135
136             @Override
137             public void onActivityDestroyed(Activity activity) {
138                 onStateChange(activity, ActivityState.DESTROYED);
139             }
140
141             @Override
142             public void onActivityPaused(Activity activity) {
143                 onStateChange(activity, ActivityState.PAUSED);
144             }
145
146             @Override
147             public void onActivityResumed(Activity activity) {
148                 onStateChange(activity, ActivityState.RESUMED);
149             }
150
151             @Override
152             public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
153
154             @Override
155             public void onActivityStarted(Activity activity) {
156                 onStateChange(activity, ActivityState.STARTED);
157             }
158
159             @Override
160             public void onActivityStopped(Activity activity) {
161                 onStateChange(activity, ActivityState.STOPPED);
162             }
163         });
164     }
165
166     /**
167      * Must be called by the main activity when it changes state.
168      *
169      * @param activity Current activity.
170      * @param newState New state value.
171      */
172     private static void onStateChange(Activity activity, int newState) {
173         if (activity == null) throw new IllegalArgumentException("null activity is not supported");
174
175         if (sActivity == null
176                 || newState == ActivityState.CREATED
177                 || newState == ActivityState.RESUMED
178                 || newState == ActivityState.STARTED) {
179             sActivity = activity;
180         }
181
182         int oldApplicationState = getStateForApplication();
183
184         if (newState == ActivityState.CREATED) {
185             assert !sActivityInfo.containsKey(activity);
186             sActivityInfo.put(activity, new ActivityInfo());
187         }
188
189         // Invalidate the cached application state.
190         sCachedApplicationState = null;
191
192         ActivityInfo info = sActivityInfo.get(activity);
193         info.setStatus(newState);
194
195         // Notify all state observers that are specifically listening to this activity.
196         for (ActivityStateListener listener : info.getListeners()) {
197             listener.onActivityStateChange(activity, newState);
198         }
199
200         // Notify all state observers that are listening globally for all activity state
201         // changes.
202         for (ActivityStateListener listener : sGeneralActivityStateListeners) {
203             listener.onActivityStateChange(activity, newState);
204         }
205
206         int applicationState = getStateForApplication();
207         if (applicationState != oldApplicationState) {
208             for (ApplicationStateListener listener : sApplicationStateListeners) {
209                 listener.onApplicationStateChange(applicationState);
210             }
211         }
212
213         if (newState == ActivityState.DESTROYED) {
214             sActivityInfo.remove(activity);
215             if (activity == sActivity) sActivity = null;
216         }
217     }
218
219     /**
220      * Testing method to update the state of the specified activity.
221      */
222     public static void onStateChangeForTesting(Activity activity, int newState) {
223         onStateChange(activity, newState);
224     }
225
226     /**
227      * @return The most recent focused {@link Activity} tracked by this class.  Being focused means
228      *         out of all the activities tracked here, it has most recently gained window focus.
229      */
230     public static Activity getLastTrackedFocusedActivity() {
231         return sActivity;
232     }
233
234     /**
235      * @return A {@link List} of all non-destroyed {@link Activity}s.
236      */
237     public static List<WeakReference<Activity>> getRunningActivities() {
238         ThreadUtils.assertOnUiThread();
239         List<WeakReference<Activity>> activities = new ArrayList<WeakReference<Activity>>();
240         for (Activity activity : sActivityInfo.keySet()) {
241             activities.add(new WeakReference<Activity>(activity));
242         }
243         return activities;
244     }
245
246     /**
247      * @return The {@link Context} for the {@link Application}.
248      */
249     public static Context getApplicationContext() {
250         return sApplication != null ? sApplication.getApplicationContext() : null;
251     }
252
253     /**
254      * Query the state for a given activity.  If the activity is not being tracked, this will
255      * return {@link ActivityState#DESTROYED}.
256      *
257      * <p>
258      * Please note that Chrome can have multiple activities running simultaneously.  Please also
259      * look at {@link #getStateForApplication()} for more details.
260      *
261      * <p>
262      * When relying on this method, be familiar with the expected life cycle state
263      * transitions:
264      * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle">
265      *   Activity Lifecycle
266      * </a>
267      *
268      * <p>
269      * During activity transitions (activity B launching in front of activity A), A will completely
270      * paused before the creation of activity B begins.
271      *
272      * <p>
273      * A basic flow for activity A starting, followed by activity B being opened and then closed:
274      * <ul>
275      *   <li> -- Starting Activity A --
276      *   <li> Activity A - ActivityState.CREATED
277      *   <li> Activity A - ActivityState.STARTED
278      *   <li> Activity A - ActivityState.RESUMED
279      *   <li> -- Starting Activity B --
280      *   <li> Activity A - ActivityState.PAUSED
281      *   <li> Activity B - ActivityState.CREATED
282      *   <li> Activity B - ActivityState.STARTED
283      *   <li> Activity B - ActivityState.RESUMED
284      *   <li> Activity A - ActivityState.STOPPED
285      *   <li> -- Closing Activity B, Activity A regaining focus --
286      *   <li> Activity B - ActivityState.PAUSED
287      *   <li> Activity A - ActivityState.STARTED
288      *   <li> Activity A - ActivityState.RESUMED
289      *   <li> Activity B - ActivityState.STOPPED
290      *   <li> Activity B - ActivityState.DESTROYED
291      * </ul>
292      *
293      * @param activity The activity whose state is to be returned.
294      * @return The state of the specified activity (see {@link ActivityState}).
295      */
296     public static int getStateForActivity(Activity activity) {
297         ActivityInfo info = sActivityInfo.get(activity);
298         return info != null ? info.getStatus() : ActivityState.DESTROYED;
299     }
300
301     /**
302      * @return The state of the application (see {@link ApplicationState}).
303      */
304     public static int getStateForApplication() {
305         if (sCachedApplicationState == null) sCachedApplicationState = determineApplicationState();
306
307         return sCachedApplicationState.intValue();
308     }
309
310     /**
311      * Checks whether or not any Activity in this Application is visible to the user.  Note that
312      * this includes the PAUSED state, which can happen when the Activity is temporarily covered
313      * by another Activity's Fragment (e.g.).
314      * @return Whether any Activity under this Application is visible.
315      */
316     public static boolean hasVisibleActivities() {
317         int state = getStateForApplication();
318         return state == ApplicationState.HAS_RUNNING_ACTIVITIES
319                 || state == ApplicationState.HAS_PAUSED_ACTIVITIES;
320     }
321
322     /**
323      * Checks to see if there are any active Activity instances being watched by ApplicationStatus.
324      * @return True if all Activities have been destroyed.
325      */
326     public static boolean isEveryActivityDestroyed() {
327         return sActivityInfo.isEmpty();
328     }
329
330     /**
331      * Registers the given listener to receive state changes for all activities.
332      * @param listener Listener to receive state changes.
333      */
334     public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
335         sGeneralActivityStateListeners.addObserver(listener);
336     }
337
338     /**
339      * Registers the given listener to receive state changes for {@code activity}.  After a call to
340      * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with
341      * {@link ActivityState#DESTROYED} all listeners associated with that particular
342      * {@link Activity} are removed.
343      * @param listener Listener to receive state changes.
344      * @param activity Activity to track or {@code null} to track all activities.
345      */
346     public static void registerStateListenerForActivity(ActivityStateListener listener,
347             Activity activity) {
348         assert activity != null;
349
350         ActivityInfo info = sActivityInfo.get(activity);
351         assert info != null && info.getStatus() != ActivityState.DESTROYED;
352         info.getListeners().addObserver(listener);
353     }
354
355     /**
356      * Unregisters the given listener from receiving activity state changes.
357      * @param listener Listener that doesn't want to receive state changes.
358      */
359     public static void unregisterActivityStateListener(ActivityStateListener listener) {
360         sGeneralActivityStateListeners.removeObserver(listener);
361
362         // Loop through all observer lists for all activities and remove the listener.
363         for (ActivityInfo info : sActivityInfo.values()) {
364             info.getListeners().removeObserver(listener);
365         }
366     }
367
368     /**
369      * Registers the given listener to receive state changes for the application.
370      * @param listener Listener to receive state state changes.
371      */
372     public static void registerApplicationStateListener(ApplicationStateListener listener) {
373         sApplicationStateListeners.addObserver(listener);
374     }
375
376     /**
377      * Unregisters the given listener from receiving state changes.
378      * @param listener Listener that doesn't want to receive state changes.
379      */
380     public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
381         sApplicationStateListeners.removeObserver(listener);
382     }
383
384      /**
385      * When ApplicationStatus initialized after application started, the onActivityCreated(),
386      * onActivityStarted() and onActivityResumed() callbacks will be missed.
387      * This function will give the chance to simulate these three callbacks.
388      */
389     public static void informActivityStarted(Activity activity) {
390         onStateChange(activity, ActivityState.CREATED);
391         onStateChange(activity, ActivityState.STARTED);
392         onStateChange(activity, ActivityState.RESUMED);
393     }
394
395     /**
396      * Registers the single thread-safe native activity status listener.
397      * This handles the case where the caller is not on the main thread.
398      * Note that this is used by a leaky singleton object from the native
399      * side, hence lifecycle management is greatly simplified.
400      */
401     @CalledByNative
402     private static void registerThreadSafeNativeApplicationStateListener() {
403         ThreadUtils.runOnUiThread(new Runnable () {
404             @Override
405             public void run() {
406                 if (sNativeApplicationStateListener != null) return;
407
408                 sNativeApplicationStateListener = new ApplicationStateListener() {
409                     @Override
410                     public void onApplicationStateChange(int newState) {
411                         nativeOnApplicationStateChange(newState);
412                     }
413                 };
414                 registerApplicationStateListener(sNativeApplicationStateListener);
415             }
416         });
417     }
418
419     /**
420      * Determines the current application state as defined by {@link ApplicationState}.  This will
421      * loop over all the activities and check their state to determine what the general application
422      * state should be.
423      * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed.
424      *         HAS_PAUSED_ACTIVITIES if none are running and one is paused.
425      *         HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped.
426      *         HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped.
427      */
428     private static int determineApplicationState() {
429         boolean hasPausedActivity = false;
430         boolean hasStoppedActivity = false;
431
432         for (ActivityInfo info : sActivityInfo.values()) {
433             int state = info.getStatus();
434             if (state != ActivityState.PAUSED
435                     && state != ActivityState.STOPPED
436                     && state != ActivityState.DESTROYED) {
437                 return ApplicationState.HAS_RUNNING_ACTIVITIES;
438             } else if (state == ActivityState.PAUSED) {
439                 hasPausedActivity = true;
440             } else if (state == ActivityState.STOPPED) {
441                 hasStoppedActivity = true;
442             }
443         }
444
445         if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES;
446         if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES;
447         return ApplicationState.HAS_DESTROYED_ACTIVITIES;
448     }
449
450     // Called to notify the native side of state changes.
451     // IMPORTANT: This is always called on the main thread!
452     private static native void nativeOnApplicationStateChange(int newState);
453 }