1 /*
2  * Copyright (C) 2018 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 package com.android.compatibility.common.util;
17 
18 import android.app.Activity;
19 import android.app.Application.ActivityLifecycleCallbacks;
20 import android.os.Bundle;
21 import android.util.ArrayMap;
22 import android.util.Log;
23 
24 import androidx.annotation.NonNull;
25 
26 import java.util.Map;
27 import java.util.concurrent.CountDownLatch;
28 import java.util.concurrent.TimeUnit;
29 
30 /**
31  * Helper object used to watch for activities lifecycle events.
32  *
33  * <p><b>NOTE:</b> currently it's limited to just one occurrence of each event.
34  *
35  * <p>These limitations will be fixed as needed (A.K.A. K.I.S.S. :-)
36  */
37 public final class ActivitiesWatcher implements ActivityLifecycleCallbacks {
38 
39     private static final String TAG = ActivitiesWatcher.class.getSimpleName();
40 
41     private final Map<String, ActivityWatcher> mWatchers = new ArrayMap<>();
42     private final long mTimeoutMs;
43 
44     /**
45      * Default constructor.
46      *
47      * @param timeoutMs how long to wait for given lifecycle event before timing out.
48      */
ActivitiesWatcher(long timeoutMs)49     public ActivitiesWatcher(long timeoutMs) {
50         mTimeoutMs = timeoutMs;
51     }
52 
53     @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)54     public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
55         Log.v(TAG, "onActivityCreated(): " + activity);
56         notifyWatcher(activity, ActivityLifecycle.CREATED);
57     }
58 
59     @Override
onActivityStarted(Activity activity)60     public void onActivityStarted(Activity activity) {
61         Log.v(TAG, "onActivityStarted(): " + activity);
62         notifyWatcher(activity, ActivityLifecycle.STARTED);
63     }
64 
65     @Override
onActivityResumed(Activity activity)66     public void onActivityResumed(Activity activity) {
67         Log.v(TAG, "onActivityResumed(): " + activity);
68         notifyWatcher(activity, ActivityLifecycle.RESUMED);
69     }
70 
71     @Override
onActivityPaused(Activity activity)72     public void onActivityPaused(Activity activity) {
73         Log.v(TAG, "onActivityPaused(): " + activity);
74         notifyWatcher(activity, ActivityLifecycle.PAUSED);
75     }
76 
77     @Override
onActivityStopped(Activity activity)78     public void onActivityStopped(Activity activity) {
79         Log.v(TAG, "onActivityStopped(): " + activity);
80         notifyWatcher(activity, ActivityLifecycle.STOPPED);
81     }
82 
83     @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)84     public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
85         Log.v(TAG, "onActivitySaveInstanceState(): " + activity);
86         notifyWatcher(activity, ActivityLifecycle.SAVE_INSTANCE);
87     }
88 
89     @Override
onActivityDestroyed(Activity activity)90     public void onActivityDestroyed(Activity activity) {
91         Log.v(TAG, "onActivityDestroyed(): " + activity);
92         notifyWatcher(activity, ActivityLifecycle.DESTROYED);
93     }
94 
95     /**
96      * Gets a watcher for the given activity.
97      *
98      * @throws IllegalStateException if already registered.
99      */
watch(@onNull Class<? extends Activity> clazz)100     public ActivityWatcher watch(@NonNull Class<? extends Activity> clazz) {
101         return watch(clazz.getName());
102     }
103 
104     @Override
toString()105     public String toString() {
106         return "[ActivitiesWatcher: activities=" + mWatchers.keySet() + "]";
107     }
108 
109     /**
110      * Gets a watcher for the given activity.
111      *
112      * @throws IllegalStateException if already registered.
113      */
watch(@onNull String className)114     public ActivityWatcher watch(@NonNull String className) {
115         if (mWatchers.containsKey(className)) {
116             throw new IllegalStateException("Already watching " + className);
117         }
118         Log.d(TAG, "Registering watcher for " + className);
119         final ActivityWatcher watcher = new ActivityWatcher(mTimeoutMs);
120         mWatchers.put(className,  watcher);
121         return watcher;
122     }
123 
notifyWatcher(@onNull Activity activity, @NonNull ActivityLifecycle lifecycle)124     private void notifyWatcher(@NonNull Activity activity, @NonNull ActivityLifecycle lifecycle) {
125         final String className = activity.getComponentName().getClassName();
126         final ActivityWatcher watcher = mWatchers.get(className);
127         if (watcher != null) {
128             Log.d(TAG, "notifying watcher of " + className + " of " + lifecycle);
129             watcher.notify(lifecycle);
130         } else {
131             Log.v(TAG, lifecycle + ": no watcher for " + className);
132         }
133     }
134 
135     /**
136      * Object used to watch for acitivity lifecycle events.
137      *
138      * <p><b>NOTE: </b>currently it only supports one occurrence for each event.
139      */
140     public static final class ActivityWatcher {
141         private final CountDownLatch mCreatedLatch = new CountDownLatch(1);
142         private final CountDownLatch mStartedLatch = new CountDownLatch(1);
143         private final CountDownLatch mResumedLatch = new CountDownLatch(1);
144         private final CountDownLatch mPausedLatch = new CountDownLatch(1);
145         private final CountDownLatch mStoppedLatch = new CountDownLatch(1);
146         private final CountDownLatch mSaveInstanceLatch = new CountDownLatch(1);
147         private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
148         private final long mTimeoutMs;
149 
ActivityWatcher(long timeoutMs)150         private ActivityWatcher(long timeoutMs) {
151             mTimeoutMs = timeoutMs;
152         }
153 
154         /**
155          * Blocks until the given lifecycle event happens.
156          *
157          * @throws IllegalStateException if it times out while waiting.
158          * @throws InterruptedException if interrupted while waiting.
159          */
waitFor(@onNull ActivityLifecycle lifecycle)160         public void waitFor(@NonNull ActivityLifecycle lifecycle) throws InterruptedException {
161             final CountDownLatch latch = getLatch(lifecycle);
162             final boolean called = latch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
163             if (!called) {
164                 throw new IllegalStateException(lifecycle + " not called in " + mTimeoutMs + " ms");
165             }
166         }
167 
getLatch(@onNull ActivityLifecycle lifecycle)168         private CountDownLatch getLatch(@NonNull ActivityLifecycle lifecycle) {
169             switch (lifecycle) {
170                 case CREATED:
171                     return mCreatedLatch;
172                 case STARTED:
173                     return mStartedLatch;
174                 case RESUMED:
175                     return mResumedLatch;
176                 case PAUSED:
177                     return mPausedLatch;
178                 case STOPPED:
179                     return mStoppedLatch;
180                 case SAVE_INSTANCE:
181                     return mSaveInstanceLatch;
182                 case DESTROYED:
183                     return mDestroyedLatch;
184                 default:
185                     throw new IllegalArgumentException("unsupported lifecycle: " + lifecycle);
186             }
187         }
188 
notify(@onNull ActivityLifecycle lifecycle)189         private void notify(@NonNull ActivityLifecycle lifecycle) {
190             getLatch(lifecycle).countDown();
191         }
192     }
193 
194     /**
195      * Supported activity lifecycle.
196      */
197     public enum ActivityLifecycle {
198         CREATED,
199         STARTED,
200         RESUMED,
201         PAUSED,
202         STOPPED,
203         SAVE_INSTANCE,
204         DESTROYED
205     }
206 }
207