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