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